wtorek, 29 marca 2011

Obsługa strony z pytaniami - Radio Buttons w ASP MVC 2

Dzisiaj trochę o stworzeniu w MVC 2 kontrolera obsługującego przyciski typu radio.
Przyciski radio button to nic innego jak przyciski wyboru. Na wielu stronach można spotkać się z listami wyboru typu: kobieta/mężczyzna. Przykład? Ot chociażby zakładanie konta pocztowego w portalu onet:


W swoim ostatnim projekcie w MVC potrzebowałem całego formularza takich kontrolek. Z pozoru zadanie było bardzo proste i sprowadzało się do:
"Potrzebna jest formatka, na której wyświetlą się przyciski TAK/NIE dla listy pytań". Tak więc należało stworzyć mechanizm, który pobiera pytania z bazy danych, tworzy listę do wyboru i zapisuje do bazy danych rezultat.
Proste i nieskomplikowane zadanie na pierwszy rzut oka okazało się nie być takie oczywiste w późniejszym etapie i kolejnych życzeniach klienta.

Oczywiście w programowaniu, tak jak i matematyce, problem można rozwiązać na wiele sposobów. Ja przedstawię tutaj dwa sposoby, które sprawdzają się w moich projektach. Pierwszy przedstawiony sposób jest bardzo podobny do tego stosowanego w PHP (bezpośrednie odwołanie się do konkretnych pól formularza), drugi używa mechanizmu bindowania wbudowanego w MVC.

Sposób I

A więc rozpoczynamy, na potrzeby tego artykułu stworzymy plik XML z pytaniami o bardzo uproszczonym schemacie. Tworzymy więc na początek prosty plik XML Questions.XML o zawartości:



<?xml version="1.0" encoding="utf-8" ?>
<Questions>
  <Question>
    <Id>1</Id>
    <Content>Czy podoba Ci się ten blog?</Content>
  </Question>
  <Question>
    <Id>2</Id>
    <Content>Lubisz swoją uczelnie?</Content>
  </Question>
  <Question>
    <Id>3</Id>
    <Content>Mieszkasz w fajnej dzielnicy?</Content>
  </Question>
  <Question>
    <Id>4</Id>
    <Content>Chodzisz do kina?</Content>
  </Question>
  <Question>
    <Id>5</Id>
    <Content>Uprawiasz sport?</Content>
  </Question>
</Questions>


Każde pytanie ma swoją treść (content) oraz niepowtarzalny identyfikator (id). Tworzymy więc bardzo prosty model naszego pytania:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RadioButtonsExample.Models
{
    public class Question
    {
        public int Id { get; set; }
        public string Content { get; set; }
    }
}

Następnie tworzymy kontroler - Home. W metodzie Index wprowadzimy następujący kod:


Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Xml.Linq;
using RadioButtonsExample.Models;

namespace RadioButtonsExample.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            XDocument xmlQuestionDocument = XDocument.Load("Questions.xml");
            IList<Question> lQuestions = new List<Question>();
            var questions = from a in xmlQuestionDocument.Elements()
                            select a;
            foreach (var item in questions.Elements("Questions.xml"))
            {
                lQuestions.Add(new Question { Id = int.Parse(item.Element("Id").Value), Content = item.Element("Content").Value });
            }

            return View(lQuestions);
        }

    }
}

Tak więc do widoku zostanie przekazana kolekcja pytań. Widok tworzymy oczywiście silnie typowany:


Code:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<IEnumerable<RadioButtonsExample.Models.Question>>" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
</head>
<body>
    <div>
    <% using (Html.BeginForm())
       { %>
       <% foreach (var item in Model)
          { %>
          <%: item.Id.ToString() + ". " + item.Content %>
          <br />
          <%: Html.RadioButton("P" + item.Id.ToString(), "T", true) %> TAK<br />
          <%: Html.RadioButton("P" + item.Id.ToString(), "N", false) %> NIE<br />
          <br />
       <% } %>
       <br />
       <br />
       <input type="submit" value="Wyślij" />
    <% } %>
    </div>
</body>
</html>

ASP.MVC posiada bardzo silny model bindingu. W łatwy sposób możemy bindować całe obiekty do formularzy i je zwracać w postaci obiektu. W tym przypadku, łatwiej jest skorzystać z kolekcji - FormCollection. Zawiera ona klucze i wartości pól na formularzu.

Wyświetlanie: zakładamy na początku najprostszą wersję:
- Wszystkie pytania mają domyślnie zaznaczoną odpowiedź.
W tym przypadku odbieramy rezultat jako kolekcję kluczy formularza. Ważne jest tu słowo kluczy - które jest utożsamione z nazwą konkretnego pola na formularzu. Pola na formularzu są budowane w sposób następujący: P{id} czyli dla pytania pierwszego mamy P1, drugiego P2 itd.
W przypadku gdy nie wybierzemy zaznaczenia domyślnego dla jednej z odpowiedzi, a użytkownik nie zaznaczy żadnej z odpowiedzi w kolekcji kluczy nie znajdziemy interesującej nas pozycji. W takiej sytuacji pozostaje sprawdzenie czy kolekcja zawiera żądaną przez nas wartość. Ja w tym celu postanowiłem stworzyć dodatkową klasę, która tworzy słownik z kontrolkami które są na formularzu i pozwala w bardzo szybki sposób sprawdzić, czy żądany klucz jest w kolekcji, czy też nie:



Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace RadioButtonsExample.Models
{
    public class Helpers
    {
        public Dictionary<string, string> GetKeyValueFormControls(FormCollection formCollection)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();
            if (formCollection != null && formCollection.Count > 0)
            {
                for (int i = 0; i < formCollection.AllKeys.Length; i++)
                {
                    result.Add(formCollection.AllKeys[i], formCollection[formCollection.AllKeys[i]]);
                }
            }

            return result;
        }
    }
}


Sposób II

Tym razem wykorzystamy możliwości bindingu wbudowane w MVC 2. Większość baz na rynku (zwłaszcza darmowych) nie posiada specjalnego pola pozwalającego zakwalifikować odpowiedź jako typ logiczny. Dwoma najprostszymi więc sposobami na uzyskanie zapisania do bazy odpowiedniej wartości takiego pola jest użycie zmiennej tekstowej typu CHAR(1) - reprezentującej zamknięty zbiór odpowiedzi {T,N} bądź też typ całkowity INTEGER o wartościach ze zbioru {0,1}. W moim przypadku wybiorę typ tekstowy.
Aby dodatkowo uprościć całość do klasy dołożę jedno dodatkowe pole, określające odpowiedź na zadane pytanie, oprócz tego utworzymy klasę Ankieta zawierającą listę pytań (tym razem w narodowym języku):



public class Pytanie
    {
        public int Id { get; set; }
        public string Tresc { get; set; }
        public string Odpowiedz { get; set; }
    }



using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MvcApplication1.Models
{
    public class Ankieta
    {
        public List<Pytanie> Pytania { get; set; }
    }
}

Dzięki temu będzie można bezpośrednio z formularza zwrócić odpowiednią wartość. Oczywiście można by stworzyć oddzielną klasę na odpowiedź, w tym prostym przypadku nie skorzystam z tej możliwości.
Przejdźmy do konstrukcji widoku dla takiego modelu. Tutaj także posłużę się dwoma przykładami widoków. Pierwszy, aby pokazać co tak naprawdę dzieje się podczas bindowania kolekcji w MVC a drugi - oczywiście upraszczający całość:
1. Sposób trudniejszy, czyli co w środku piszczy:



Code:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<MvcApplication1.Models.Ankieta>" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
</head>
<body>
    <div>
        <% using (Html.BeginForm())
           { %>
           <%  for(int i = 0; i < Model.Pytania.Count; i++)
              { %>
              <%: Html.Hidden(string.Format("Pytania[{0}].ID", i), Model.Pytania[i].Id) %>
              <%: Html.DisplayFor(x => x.Pytania[i  ].Tresc) %>
              <br />
              <%: Html.RadioButton(string.Format("Pytania[{0}].Odpowiedz", i), "T") %>TAK
              <br />
              <%: Html.RadioButton(string.Format("Pytania[{0}].Odpowiedz", i), "N") %>NIE
              <br />
              <br />
           <% } %>

           <input type="submit" value="Wyślij" />
        <% } %>
    </div>
</body>
</html>

Sprawdźmy od razu co generuje ten kod w HTML:



Code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>
 Index
</title></head>
<body>
    <div>
        <form action="/" method="post"><input id="Pytania_0__ID" name="Pytania[0].ID" type="hidden" value="1" />
              Czy lubisz te strone?
              <br />
              <input id="Pytania_0__Odpowiedz" name="Pytania[0].Odpowiedz" type="radio" value="T" />TAK
              <br />
              <input id="Pytania_0__Odpowiedz" name="Pytania[0].Odpowiedz" type="radio" value="N" />NIE
              <br />
              <br />
           <input id="Pytania_1__ID" name="Pytania[1].ID" type="hidden" value="2" />
              Czy chcialbys pomoz tworzyc te strone?
              <br />
              <input id="Pytania_1__Odpowiedz" name="Pytania[1].Odpowiedz" type="radio" value="T" />TAK
              <br />
              <input id="Pytania_1__Odpowiedz" name="Pytania[1].Odpowiedz" type="radio" value="N" />NIE
              <br />
              <br />
           

           <input type="submit" value="Wyślij" />
        </form>
    </div>
</body>
</html>

Zwróćmy uwagę na tworzenie nazwy kontrolki. Mechanizm Bindingu w MVC nie tylko musi znać dokładną nazwę pola do którego binduje dany element strony, ale także pozycję w kolekcji. Dlatego też aby zwrócić poprawnie dane z formularza jako obiekt Ankieta:


Code:
<input id="Pytania_1__Odpowiedz" name="Pytania[1].Odpowiedz" type="radio" value="T" />TAK

Sama nazwa generowana jest w prosty sposób:


Code:
<%: Html.RadioButton(string.Format("Pytania[{0}].Odpowiedz", i), "T") %>TAK

Co prawda nie jest problemem pisanie takiego kodu, jednak po pewnym czasie staje się on mało czytelny (jak również dla kolejnych programistów tego projektu z pewnością nie będzie wygodnie domyślać się o co chodziło autorowi). Można całość uprościć do wyrażenia lambda:


Code:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<MvcApplication1.Models.Ankieta>" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
</head>
<body>
    <div>
        <% using (Html.BeginForm())
           { %>
           <%  for(int i = 0; i < Model.Pytania.Count; i++)
              { %>
              <%: Html.HiddenFor(x => x.Pytania[i].Id) %>.
              <%: Html.DisplayFor(x => x.Pytania[i].Tresc) %>
              <br />
              <%: Html.RadioButtonFor(x => Model.Pytania[i].Odpowiedz, "T")  %>TAK
              <br />
              <%: Html.RadioButtonFor(x => Model.Pytania[i].Odpowiedz, "N") %>NIE
              <br />
              <br />
           <% } %>

           <input type="submit" value="Wyślij" />
        <% } %>
    </div>
</body>
</html>

Oczywiście wygenerowany kawałek kodu HTML nie będzie się niczym różnił od poprzedniego, ale nie zawiera elementów gimnastyki dla zaawansowanych podczas tworzenia nazwy elementu HTML.

Sposób którzy obierzemy zależy oczywiście tylko od naszych preferencji. Ja przeważnie stosuje drugi, wykorzystujący binding. Uważam, że jest to bardziej poprawny sposób bindowania danych w frameworku, który właściwie słynie z łatwości bidningu całych obiektów do formularzy HTML.

Ktoś dociekliwy może posunąć się dalej i stworzyć własny HTML helper, generujący odpowienie bloki przycisków typu radio z odpowiednimi wartościami korzystając z list, typów wyliczeniowych itp. Ograniczeniem w tym przypadku jest tylko i wyłączenie wyobraźnia.

niedziela, 27 marca 2011

Aplikacja z wykorzystaniem SqLite oraz WPF

Jak we wcześniejszym poście obiecałem, zamieszczę aplikację wykonaną przy użyciu bazy SqLite oraz technologi WPF o której już sporo się przewinęło na tym blogu.

Zadaniem było stworzenie programu umożliwiającego gromadzenie linków do stron, podzielonych na kategorie według uznania użytkownika.

Rozpoczynamy od schematu bazy danych:




Jak widać, 3 tabele:
Url - opisuje link
Category - kategoria danego linka
UrlCategory - tabela techniczna - umożliwia przypięcie linka do wielu kategorii

Interfejs programu zostanie napisany przy użyciu WPF.

Cały program wygląda następująco:

Wykorzystałem komponent Ribbon - wstążka znany z Microsoft Office. Użytkownik ma możliwość wyszukiwania linków po opisie.
Dodawanie kategorii:


Dodawanie linku (wcześniej wybranie kategorii do której będzie należał link):






Implementacja:
Podczas tworzenia kodu programu nie używałem na sztywno providera. Tak więc zamiast SqLiteConnection użyłem DbConnection. Pozwala to na szybkie przełączanie się między różnymi bazami danych. Klient zmieniając tylko odpowiednio ConnectionString może użyć własnej bazy danych. Zobaczmy na przykład kodu napisanego właśnie w takim stylu:

        public IList<Category> GetAllCategories()
        {
            IList<Category> categories = new List<Category>();
            using (DbConnection dbconn = dbProvider.CreateConnection())
            {
                using (DbCommand dbcmd = dbProvider.CreateCommand())
                {
                    dbconn.ConnectionString = connectionString.ConnectionString;
                    dbcmd.Connection = dbconn;
                    dbcmd.CommandText = @"SELECT IdCategory, CategoryName
                                            FROM Category";
                    dbconn.Open();
                    using (DbDataReader dr = dbcmd.ExecuteReader())
                    {
                        while (dr.Read())
                        {
                            Category tmp = new Category();
                            tmp.CategoryName = (string)dr["CategoryName"];
                            tmp.IdCategory = int.Parse(dr["IdCategory"].ToString());
                            categories.Add(tmp);
                        }
                    }
                }
            }
            return categories;
        }

W całej aplikacji parametry przekazywane są poprzez obiekt DbParameter, unikamy w ten sposób zawiłych łączeń kodu, jak i to co idzie do bazy nie zawiera niebezpieczeństwa SQL injection (wstrzyknięcie złośliwego kodu). Przykład takiego kodu:

       public IList<Url> GetUrlsByCategoryId(int idCategory)
        {
            IList<Url> urls = new List<Url>();
            using (DbConnection dbconn = dbProvider.CreateConnection())
            {
                using (DbCommand dbcmd = dbProvider.CreateCommand())
                {
                    dbcmd.Connection = dbconn;
                    dbconn.ConnectionString = connectionString.ConnectionString;
                    dbcmd.CommandText = @"SELECT u.IdUrl, Url,Description
                                            FROM Url u JOIN UrlCategory uc ON u.IdUrl = uc.IdUrl JOIN Category c ON uc.IdCategory = c.IdCategory
                                            WHERE c.IdCategory = @IdCategory";
                    DbParameter idCategoryParameter = dbcmd.CreateParameter();
                    idCategoryParameter.DbType = DbType.Int32;
                    idCategoryParameter.ParameterName = "@IdCategory";
                    idCategoryParameter.Value = idCategory;
                    dbcmd.Parameters.Add(idCategoryParameter);
                    dbconn.Open();
                    using (DbDataReader dr = dbcmd.ExecuteReader())
                    {
                        while (dr.Read())
                        {
                            Url tmp = new Url();
                            tmp.IdUrl = int.Parse(dr["IdUrl"].ToString());
                            tmp.Description = (string)(dr["Description"] == DBNull.Value ? "" : dr["Description"]);
                            tmp.UrlString = (string)(dr["Url"] == DBNull.Value ? "" : dr["Url"]);
                            urls.Add(tmp);
                        }
                    }
                }
            }
            return urls;
        }


Wygląd elementów ListBox został zmieniony poprzez zastosowanie odpowiedniego stylu dla nich:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="lbxItemStyle" TargetType="{x:Type ListBoxItem}">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="Padding" Value="2,0,0,0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListBoxItem}">
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="0:0:1"/>
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="MouseOver">
                                    <Storyboard>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.OpacityMask).(GradientBrush.GradientStops)[1].(GradientStop.Offset)" Storyboard.TargetName="contentPresenter">
                                            <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Offset)" Storyboard.TargetName="border">
                                            <EasingDoubleKeyFrame KeyTime="0" Value="0.293"/>
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled"/>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="border" BorderBrush="#FF93A3E5" BorderThickness="2" Margin="0" CornerRadius="10">
                            <Border.Background>
                                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                    <GradientStop Color="Black"/>
                                    <GradientStop Color="#FF92A2CA" Offset="1"/>
                                </LinearGradientBrush>
                            </Border.Background>
                            <ContentPresenter x:Name="contentPresenter" HorizontalAlignment="Center" VerticalAlignment="Center">
                                <ContentPresenter.OpacityMask>
                                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                        <GradientStop Color="Black" Offset="0"/>
                                        <GradientStop Color="White" Offset="1"/>
                                    </LinearGradientBrush>
                                </ContentPresenter.OpacityMask>
                            </ContentPresenter>
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="true">
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsSelected" Value="true"/>
                                <Condition Property="Selector.IsSelectionActive" Value="false"/>
                            </MultiTrigger.Conditions>
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                        </MultiTrigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <!-- Resource dictionary entries should be defined here. -->
</ResourceDictionary>


Dodatkowe uwagi:
Należy pamiętać, że korzystając z providera który nie jest dostępny domyślnie na platformie .NET należy dodać go do odpowiedniej sekcji w pliku App.config bądź też bezpośrednio zmodyfikować plik Machine.config na maszynie klienta. W obu rozwiązaniach należy dodać następujący wpis:

  <system.data>
    <DbProviderFactories>
      <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".Net Framework Data Provider for SQLite"
           type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
    </DbProviderFactories>
  </system.data>


Kolejnym ważnym punktem jest udostępnienie wraz z exe aplikacji odpowiedniej biblioteki umożliwiającej korzystanie z SqLite:



Co brakuje w projekcie a przydałoby się dorobić? Oczywiście jest cała lista takich rzeczy (które są na tzw. liście do dokończenia kiedyś :) ). Projekcik miał za zadanie pokazać jak posługiwać się bazą SqLite brakuje więc w nim kilku moim zdaniem przydatnych rzeczy:
- możliwosć dodawania do wielu kategorii
- skórki dla grida
- większa obsługa klawiatury w aplikacji (szybciej coś wyklikać na klawiaturze niż namierzyć myszką :) )
- logowanie do aplikacji
- eksport linków do pliku itp.
- poprawienie wizualnego wyglądu aplikacji poprzez zastosowanie motywów (kiedyś opiszę jak je wykorzystuję w aplikacji Silverlight którą ostatnio tworzyłem)

Projekt w obecnym stanie można pobrać i przetestować stąd.
Życzę udanego czytania kodu jak i eksperymentowania poprzez dodawanie własnych modułów/rozszerzanie istniejącej funkcjonalności.

Oczywiście w razie pytań służę pomocą.

SqLite w .NET

Dziś coś o bazie danych - SqLite. Z pewnością niewielu z Was spotkało się z tą bazą, a Ci którzy spotkali się z nią najczęściej szybko ją porzucają dla komercyjnych baz typu Oracle, MS SQL czy PostgreSql.

Prawdą jest, że w większości systemów z którymi pracuje na co dzień opartych jest na którymś z komercyjnych silników bazodanowych (najczęściej Oracle, MS SQL), jednak warto baczniej przyjrzeć się bazie SqLite.

Na początek słów kilka o samej bazie danych:
Zalety:
- rozmiar: około 300 kB
- napisana w C
- brak dodatkowego procesu zarządzającego relacyjną bazą danych
- zero konfiguracji - nie instalujemy jej, brak potrzeby zarządzania przez administratora
- implementuje większość standardu SQL92
- cała baza danych w jednym pliku (do 2TB)
- zapewnia transakcyjność (ACID)

Wady:
- niepełne wsparcie dla instrukcji ALTER TABLE - brak możliwości modyfikowania istniejących pól, oraz ich usuwania
- staje się coraz wolniejsza dla dużych baz danych (zauważalnie powyżej 2GB)
- odczyt może prowadzić wiele procesów na raz, jednak zapis może być prowadzony tylko przez jeden proces na raz (zakładany jest look na bazę danych).

Podsumowując, jak na wady i zalety nie jest tak źle. Wielu producentów korzysta z tej bazy jako alternatywy dla plików konfiguracyjnych np: Adobe, Firefox, McAfee, Skype itd...

W mojej opinii baza sprawdza się świetnie zwłaszcza w dwóch płaszczyznach: małe aplikacje desktopowe, niewielkie witryny (nie potrzebujemy nawet konta oferującego bazę danych), wersje demonstracyjne systemów. Ostatnie zastosowanie wydaje się bardzo kuszące. Jak wiadomo, klient zanim coś kupi - chce to zobaczyć. W łatwy sposób można przygotować aplikację demo, która pokaże w jaki sposób działa system a przy okazji nie zmusi do wielogodzinnego konfigurowania Oracla czy innego systemu bazodanowego. Po prostu wysyłamy exe wraz z bazą SqLite a klient od razu może przyjrzeć się naszej aplikacji.

Aby skorzystać z dobrodziejst SqLite w .NET potrzebujemy odpowiedniego providera dla tej bazy. Nie ma sensu pisania własnego gdyż istnieje już gotowy - zalecany przez twórców bazy. Pobrać go możemy ze strony http://sqlite.phxsoftware.com/ Po zainstalowaniu mamy do dyspozycji przestrzeń System.Data.SQLite gdzie znajdują się odpowiednie klasy do pracy z naszą bazą danych. Tworzenie pliku bazy danych po instalacji bazy możliwe jest zarówno poprzez dołączony do bazy shell jak i poprzez wygodny wizualny edytor Visual Studio:




Cała praca z bazą SqLite nie odbiega znacząco od pracy z innymi bazami danych. W zależności od tego w jaki sposób pracujemy z bazą danych tworzymy połączenie odczytujemy dane, zamykamy połączenie. 


Przykład:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using System.Configuration;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            //1. Odczyt danych:
            ConnectionStringSettings connstring = ConfigurationManager.ConnectionStrings["db"];
            using (SQLiteConnection conn = new SQLiteConnection(connstring.ConnectionString))
            {
                using (SQLiteCommand cmd = new SQLiteCommand(conn))
                {
                    conn.Open();
                    cmd.CommandText = "SELECT firstName, lastName FROM person";
                    using (SQLiteDataReader dr = cmd.ExecuteReader())
                    {
                        while (dr.Read())
                        {
                            Console.WriteLine(dr["firstName"]);
                            Console.WriteLine(dr["lastName"]);
                        }
                    }
                }
            }

            //2. Zapis do bazy
            using (SQLiteConnection conn = new SQLiteConnection(connstring.ConnectionString))
            {
                using (SQLiteCommand cmd = new SQLiteCommand(conn))
                {
                    conn.Open();
                    cmd.CommandText = "INSERT INTO person(firstName, lastName) VALUES(@firstName, @lastName)";
                    cmd.Parameters.AddWithValue("@firstName", "Jan");
                    cmd.Parameters.AddWithValue("@lastName", "Kowalski");
                    cmd.ExecuteNonQuery();
                }
            }

            //3. Usuwanie danych z bazy
            using (SQLiteConnection conn = new SQLiteConnection(connstring.ConnectionString))
            {
                using (SQLiteCommand cmd = new SQLiteCommand(conn))
                {
                    conn.Open();
                    cmd.CommandText = "DELETE FROM person WHERE lastName='@lastName')";
                    cmd.Parameters.AddWithValue("@lastName", "Kowalski");
                    cmd.ExecuteNonQuery();
                }
            }

            //4. Aktualizacja danych
            using (SQLiteConnection conn = new SQLiteConnection(connstring.ConnectionString))
            {
                using (SQLiteCommand cmd = new SQLiteCommand(conn))
                {
                    conn.Open();
                    cmd.CommandText = "UPDATE person SET firstName='@firstName' WHERE lastName='@lastName')";
                    cmd.Parameters.AddWithValue("@lastName", "Kowalski");
                    cmd.Parameters.AddWithValue("@firstName", "Marek");
                    cmd.ExecuteNonQuery();
                }
            }
        }
    }
}



Warto zapamiętać, że jeżeli tworzymy kod, który ma za zadanie działać na wielu rekordach na raz, należy zawsze używać transakcji. Nie używając transakcji operacja wstawiania rekordów może trwać nawet 10 razy dłużej! Przykład zaczerpnięty z podręcznika SqLite:

            using (SQLiteCommand mycommand = new SQLiteCommand(myconnection))
            {
                int n;

                for (n = 0; n < 100000; n++)
                {
                    mycommand.CommandText = String.Format("INSERT INTO [MyTable] ([MyId]) VALUES({0})", n + 1);
                    mycommand.ExecuteNonQuery();
                }
            }

kod powyżej powinien zostać zastąpiony (i zapomniany) następującym:
            using (SQLiteTransaction mytransaction = myconnection.BeginTransaction())
            {
                using (SQLiteCommand mycommand = new SQLiteCommand(myconnection))
                {
                    SQLiteParameter myparam = new SQLiteParameter();
                    int n;

                    mycommand.CommandText = "INSERT INTO [MyTable] ([MyId]) VALUES(?)";
                    mycommand.Parameters.Add(myparam);

                    for (n = 0; n < 100000; n++)
                    {
                        myparam.Value = n + 1;
                        mycommand.ExecuteNonQuery();
                    }
                }
                mytransaction.Commit();
            }


W kolejnym poście pokażę przykładową (realną) aplikację napisaną z użyciem powyższej bazy danych.

czwartek, 24 marca 2011

Silverlight Toolkit - Charting

Jak wcześniej napisałem, zamierzam pociągnąć serię artykułów nt. Silverlight. Samą definicję co to jest Silverlight można odnaleźć w internecie (np w wikipedii - http://en.wikipedia.org/wiki/Microsoft_Silverlight). Silverlight działa w podobny sposób jak adobe flash. Instaluje się jako plugin do przeglądarki (ok. 4 MB do pobrania) i w założeniu jest multiplatformowy. Myślę, że na więcej informacji teoretycznych przyjdzie jeszcze czas, teraz przejdźmy do sedna :)

Podczas obecnie tworzonego projektu potrzebowałem przedstawić klientowi dane w formie wykresu. Jak wiadomo, jeden diagram potrafi powiedzieć więcej niż 4 strony tekstu.
Pomocny okazał się pakiet całkowicie darmowych kontrolek Silverlight toolkit, który można pobrać ze strony - http://silverlight.codeplex.com/
Zawiera on szereg dodatkowych kontrolek, które ułatwiają codzienną pracę dewelopera. Pakiet zawiera także kilka rodzajów wykresów z których skorzystałem podczas tworzenia aplikacji.

Dane w aplikacji którą tworzyłem (i w dalszym ciągu rozwijam) pobierane są z webserwisu (WCF). W przykładzie obrazującym działanie wykresów, wykorzystam prostsze rozwiązanie, polegające na zwrócenie danych przez metodę klasy. Jako rodzaj wykresów wybrałem wykres kołowy.

Tak więc przystępujemy do działania:
1. Na początek tworzymy nowy projekt Silverlight w Visual Studio 2010
2. Dodajemy na formatkę element Chart oraz PieSeries:
3. Stworzymy naszą klasę która zwróci dane do wyświetlenia na wykresie. Będzie to prosta klasa zwracająca średnie wynagrodzenie kwartalnie:


        public IEnumerable<SalaryItem<double>> GetQuarterlySalary(double startValue)
        {
            IList<SalaryItem<double>> quarterlySalary = new List<SalaryItem<double>>();
            for (int i = 1; i < 13; i += 4)
            {
                quarterlySalary.Add(new SalaryItem<double> { ItemName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(i), Value = startValue });
                startValue *= 1.02;
            }

            return quarterlySalary;
        }

Składnik wypłaty to prosta klasa z dwoma właściwościami:

    public class SalaryItem<T>
    {
        public string ItemName { get; set; }
        public T Value { get; set; }
    }

5. Źródło danych podłączymy po załadowaniu aplikacji silverlight:

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            PieSeries pie = chrtAverageSalary.Series[0] as PieSeries;
            if (pie != null)
            {
                pie.ItemsSource = new AverageSalary().GetQuarterlySalary(3400);  
            }
        }

6. W kodzie XAML dodajemy informacje, jakie pole odpowiada w źródle danych za nazwę w legendzie jak i która właściwość to wartość dla danego składnika:

        <toolkit:Chart Name="chrtAverageSalary" Title="Average Salary">
            <toolkit:PieSeries IndependentValueBinding="{Binding ItemName}" DependentValueBinding="{Binding Value}">
            </toolkit:PieSeries>
        </toolkit:Chart>

9. Całość wygląda następująco po uruchomieniu:

Prosty wykres stworzony. Dostajemy także możliwość podglądu wartości koła poprzez najechanie na odpowiednią część.

W dalszej części artykułu zostaną omówione bardziej skomplikowane aspekty, głównie związane z sposobem prezentacji danych.

niedziela, 13 marca 2011

Obrona pracy inżynierkisej

Witam po dłuższej przerwie.
Czytelnicy mogli zauważyć że przez dłuższy czas nie umieszczałem na blogu żadnych wpisów. Spowodowane to było moimi przygotowaniami do obrony pracy inżynierskiej. Jako że wszystko poszło znakomicie mogę na spokojnie wrócić do blogowania.
A co w przyszłych wpisach czeka? Planów sporo a co uda się zrealizować? Zobaczymy.
Jeżeli chodzi o plany to przede wszystkim:
- Zakończenie serii związanej z wzorcami projektowymi
- Silverlight 4:
  • Budowa aplikacji
  • Użycie WCF RIA Seriveces
  • Silverlight Business Application
- Dokończenie serii o WPF (informacje przydadzą się także w Silverlighcie gdyż oba frameworki opierają się o "prawie" takie same klasy i umożliwiają "prawie" to samo)
- ASP MVC 3

Lista długa, co uda się zrealizować? Czas pokaże. Postaram się także skupić bardziej od strony praktycznej na omawianych zagadnieniach, aby pokazać w jaki sposób wykorzystuje w moich projektach opisywane tutaj zagadnienia.

Pozdrawiam.