niedziela, 27 stycznia 2013

[WPF] Caliburn.Micro: Kolekcje

Podczas bindowania kolekcji najpierw należy zdefiniować model danych. Przykładowa klasa reprezentująca dane osoby.

public class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
    public bool IsMale { get; set; }
}

Następnie w ViewModelu definiujemy obserwowalną kolekcję, która zapewni nam synchronizację widoku z modelem danych.

public class PersonListViewModel : Screen
{
    public ObservableCollection<Person> Items { get; set; }

    private Person _selectedItem;
    public Person SelectedItem
    {
        get { return _selectedItem; }
        set { _selectedItem = value; }
    }

    public PersonListViewModel()
    {
        Items = new ObservableCollection<Person>
        {
            new Person{ Age = 15, IsMale = true, Name= "Michael"},
            new Person{ Age = 80, IsMale = true, Name= "Steven"},
            new Person{ Age = 25, IsMale = false, Name= "Kate"},
            new Person{ Age = 64, IsMale = true, Name= "Matthew"}

        };
    }
}

Dodatkowo, dzięki konwencjom, property SelectedItem będzie synchronizowane z wybranym elementem z kolekcji Items.
Ostatni krok to binding w ViewModelu. Kolekcje można wyświetlać w kilku elementach WPFa. Poniżej pokazano, jak to zrobić w ListBoxie.

<ListBox x:Name="Items" 
         cal:Message.Attach="[Event SelectionChanged] = [Action NewSelectedItem($eventargs)]"
         SelectionMode="Single">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Width="200" Height="100" cal:Bind.Model="{Binding}" >
                <TextBlock x:Name="Name" VerticalAlignment="Top" />
                <Label x:Name="Age" VerticalAlignment="Top" />
                <CheckBox x:Name="IsMale" VerticalAlignment="Top" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Podobnie jak w przypadku skalarnych propercji, zaczynamy od ustawienia atrybutu x:Name. Dzięki cal:Message.Attach możemy przypisywać do zdarzeń widoku funkcje z ViewModelu. Następnie dla całego ListBoxa definiuje się ItemTemplate, w którym na kontenerze zaznaczamy binding do propercji poprzez atrybut cal:Bind.Model. Poziom niżej w drzewie wizualnym możemy już bindować się do pojedynczych propercji poprzez standardowe x:Name.

sobota, 26 stycznia 2013

[WPF] Caliburn.Micro: Wprowadzenie

Caliburn.Micro to prosty a zarazem dający wiele możliwości framework dla aplikacji tworzonych w oparciu o XAML i wzorzec MVVM. Aby skorzystać z Caliburna należy doinstalować go do naszego projektu przy pomocy managera pakietów NuGet z VisualStudio.



Jedną z najlepszych funkcjonalności jest data binding rozwiązany poprzez konwencje. Aby powiązać ze sobą View oraz ViewModel, programista nie musi dopisywać żadnych specjalnych instrukcji. Wszystko zapewnia nam zachowanie odpowiedniej konwencji nazw dla naszych klas. Przykładowo, domyślny widok startowy po zainstalowaniu Caliburna to ShellView. Aby bindować do niego dane jako propercje ViewModelu wystarczy nazwać go zamieniając View na ViewModel.

<Window x:Class="CaliburnMicroSampleWPF.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cal="http://www.caliburnproject.org"
        Width="400" Height="400"
        >
 <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        
        <ContentControl x:Name="Item" Grid.Column="0">            
        </ContentControl>
        <ContentControl x:Name="Details" Grid.Column="1">            
        </ContentControl>
    </Grid>
</Window>


[Export(typeof(IShell))]
    public class ShellViewModel :  Screen, IShell 
    {
        private object _item;
        public object Item 
        {
            get { return _item; }
            set
            {
                _item = value;
                NotifyOfPropertyChange("Item");
            }
        }
        public object Details { get; set; }

        public ShellViewModel()
        {
            Item = new PersonListViewModel();
            Details = new PersonDetailsViewModel();
        }
    }

Elementowi z XAML, który ma ustawiony atrybut x:Name, odpowiada property z ViewModelu o takiej samej nazwie. Tak działa data binding w Caliburnie. Powyższy kod pokazuje dodatkowo, jak wstawiać podwidoki do widoku okna. Wystarczy po stronie widoku skorzystać z ContentControl, a po stronie ViewModelu stworzyć properties typu object. To wystarczy do zbindowania danych, natomiast wstawieniem konkretnego podwidoku można sterować poprzez przypisywanie do propercji odpowiednich ViewModeli.

O tym, który widok będzie wyświetlany przy starcie aplikacji decyduje klasa AppBootstrapper.cs, a konretnie jej nagłówek:

public class AppBootstrapper : Bootstrapper<IShell>

W tym przypadku uruchamiany będzie widok, którego ViewModel dziedziczy z interfejsu IShell.

piątek, 25 stycznia 2013

Bazy danych

Wszystko na temat baz danych: zarówno relacyjnych, wraz z narzędziami ORM, jak i nowego trendu NoSQL. Spis postów na temat ORM i SQL

[SQL|ORM] Entity Framework : Code First

Code First to rozwiązanie zaproponowane w EF 4.1, wydanym w kwietniu 2011. Nie jest zatem częścią .NETa 4.0 i VS 2010. Aby skorzystać z tej wersji EF w Visual Studio należy doinstalować najnowszą wersję EF (obecnie 5.0), najlepiej przy użyciu menadżera pakietów NuGet.

W podejściu CodeFirst, modelem danych stają się klasy użytkownika, które można przetransformować na tabele relacyjnej bazy danych. Główna różnica polega na tym, że nie mamy już do czynienia z plikami .edmx. Metadane dla klas tworzone są w pamięci, a nie w plikach xmlowych. Tabele, które zostaną stworzone możemy konfigurować przy użyciu atrybutów nad propercjami lub całymi klasami. Drugie podejście to tzw. Fluent API, czyli konfiguracja poprzez wywołania specjalnych funkcji.

Poniżej prosty przykład jak stworzyć tabelę w CodeFirst. Domyślnie, jeżeli nie skonfigurujemy connectionString, utworzy nam się baza danych w SQL Express o nazwie złożonej z nazwy namespace i nazwy klasy kontekstu.

public class Blog
    {
        public Blog()
        {
            Posts = new List<Post>();
        }

        public int Id { get; set; }
        public string Title { get; set; }
        public string BloggerName { get; set; }
        public List<Post> Posts { get; set; }
        public string BlogCode
        {
            get { return Title.Substring(0, 1) + ":" + BloggerName.Substring(0, 1); }
        }
    }

public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public virtual List<Blog> Blogs { get; set; }
    }

public class Context : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
    }

var db = new Context();
var blog = new Blog() {BloggerName = "Julie", Title = "EFBlog"};
db.Blogs.Add(blog);
db.SaveChanges();

Klasę kontekstu tworzymy dziedzicząc po typie DbContext wprowadzonym również w EF4.1. Klucze główne dla encji ustawiane są przez konwencję, tak więc np. dla klasy Blog, kluczem głównym będzie property o nazwie Id lub BlogId. Dla tabeli Post utworzony zostanie również domyślnie klucz obcy do tabeli Blog. Ponadto warto skonfigurować program tak, aby przy każdej zmianie modelu baza tworzyła się na nowo. Przy starcie aplikacji dodać należy następujące wyrażenie.

Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());

Gdzie Context to nazwa klasy dziedziczącej po DbContext.

Data Annotations

Data Annotations daje możliwość konfiguracji klas domenowych. W tym podejściu konfiguracja odbywa się poprzez atrybuty. Poniżej lista ciekawszych atrybutów
  • [Key] -  klucz główny na kolumnie nie obejmowanej konwencją
  • [MaxLength(int)] - ustawienie dotyczące ilości znaków dla danej kolumny
  • [Required] - mapowane na NOT NULL w SQL
  • [Column(String)] - mapowanie na kolumnę o danej nazwie
  • [Table(String)] - klasa mapowana na tabelę o podanej nazwie
  • [DatabaseGenerated(DatabaseGeneratedOption)] - można tutaj ustawić np Identity dla klucza głównego
  • [NotMapped] - nie transformowane do bazy danych
  • [ComplexType] - typy złożone jako propercje, poszczególne properties takiego typu zostaną przeklejone do tabeli odpowiadającej encji, w której taki typ się znajduje.
  • [ForeignKey(String)] - nazwa klucza obcego umieszczana nad referencją
public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    [Required]
    public string Title { get; set; }
    [MaxLength(30)]
    public string BloggerName { get; set; }
    public List<Post> Posts { get; set; }
    [NotMapped]
    public string BlogCode
    {
        get { return Title.Substring(0, 1) + ":" + BloggerName.Substring(0, 1); }
    }
}

Fluent API

Wszystkie opcję dostępne z poziomu DataAnnotations  można konfigurować także przy użyciu tzw. FluentAPI, czyli po prostu korzystając z odpowiednich klas i metod.Dzięki takiemu podejściu nasze "Domain Objects" pozostaną niezależne od konfiguracji bazy danych.

public class Context : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        var blog = modelBuilder.Entity<Blog>();
        blog.Property(b => b.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        blog.Property(b => b.Title).IsRequired();
        blog.Property(b => b.BloggerName).HasMaxLength(30);
        blog.Ignore(b => b.BlogCode);
        base.OnModelCreating(modelBuilder);
    }
}

FluentAPI udostępnia także kilka dodatkowych opcji niedostępnych z poziomu DataAnnotations, na przykład możliwość dzielenia encji na wiele tabel lub łączenia wielu encji w jedną tabelę.

niedziela, 13 stycznia 2013

[SQL|ORM] Entity Framework : Obiekty Entity Framework

Obiekty EF można dostosować do potrzeb aplikacji na bardzo wiele sposobów. W tym poście kilka użytecznych przykładów w tym temacie.

Wykonywanie własnego kodu, podczas wywołania metody SaveChanges

Wystarczy nadpisać metodę SaveChanges z obiektu kontekstu. Stan obiektów dostępny jest w ObjectStateManager.

public override int SaveChanges(SaveOptions options)
        {
            Console.WriteLine("Saving Changes");
            var applicants = this.ObjectStateManager
                                .GetObjectStateEntries(EntityState.Deleted)
                                .Select(e => e.Entity)
                                .OfType<Applicant>().ToList();
            int changes = base.SaveChanges(options);
            Console.WriteLine("\n{0} applicants deleted",
                applicants.Count().ToString());

            foreach (var applicant in applicants)
            {
                File.Delete(applicant.ResumePath);
                Console.WriteLine("\n{0}File deleted at {1}", applicant.Name,
                    applicant.ResumePath);
            }
            return changes;
        }

Reguły walidacyjne dla propercji 

Aby dodać prostą regułę walidacyjną należy na obiekcie encji nadpisać metody zgodne z nazwą property. Metody te generowane są przez EF. Na przykład dla property UserName mamy możliwe OnUserNameChanging oraz OnUserNameChanged. W tych metodach mamy dostęp do encji, więc możemy modyfikować jej pola. Metody takie wołane są nie tylko przy zmianie wartości property ale także przy materializowaniu encji podczas jej pobierania z bazy danych.

public partial class User
{
    partial void OnUserNameChanging(string value)
    {
        if (value.Length > 5)
            Console.WriteLine("{0}'s UserName changing to {1}, OK!",
                this.FullName, value);
        else
            Console.WriteLine("{0}'s UserName changing to {1}, TooShort!",
                this.FullName, value);
    }

    partial void OnUserNameChanged()
    {
        this.IsActive = this.UserName.Length > 5;
    }
}

Logowanie połączeń do bazy danych

Aby logować połączenia możemy zasubskrybować się na zdarzenie StateChange z property Connection dostępnej w obiekcie kontekstu.

this.Connection.StateChange += (s, e) =>
    {
        var conn = ((EntityConnection) s).StoreConnection;
        Console.WriteLine("{0}: Database: {1}, State: {2} was {3}",
                            DateTime.Now.ToShortTimeString(), conn.Database,
                            e.CurrentState, e.OriginalState);
    };

Walidacja przy SaveChanges

Czasami do walidacji potrzebujemy dwie wartości encji: nową i poprzednią (np. procentowy przyrost płac). Odpowiednim miejscem, w którym mamy dostęp do obu wartości jest zdarzenie SavingChanges.

public partial class EntityFrameworkRecipesEntities3
{
    partial void OnContextCreated()
    {
     this.SavingChanges += new EventHandler(EntityFrameworkRecipesEntities3_SavingChanges);
    }

    void EntityFrameworkRecipesEntities3_SavingChanges(object sender, EventArgs e)
    {
     var entries = this.ObjectStateManager
         .GetObjectStateEntries(EntityState.Modified)
         .Where(entry => entry.Entity is Employee);
     foreach(var entry in entries)
     {
      var salaryProp = entry.GetModifiedProperties()
           .FirstOrDefault(p => p == "Salary");
      if(salaryProp != null)
      {
       var originalSalary = Convert.ToDecimal(
            entry.OriginalValues[salaryProp]);
       var currentSalary = Convert.ToDecimal(
            entry.CurrentValues[salaryProp]);
       if(originalSalary != currentSalary)
       {
        if(currentSalary > 1.1M*originalSalary)
        {
         throw new ApplicationException("Can't increase salary more than 10%");
        }
       }
      }

     }
    }
}

niedziela, 30 grudnia 2012

[SQL|ORM] Entity Framework : Stored Procedures

Procedury składowane to narzędzie, z którego bardzo często warto skorzystać. Przeniesienie niektórych obliczeń na serwer bazy danych może usprawnić działanie aplikacji i uporządkować ją z logicznego punktu widzenia. Ponieważ procedury "żyją" po stronie bazy danych, należy wiedzieć, jak wywoływać je z poziomu narzędzia ORM. Poniżej kilka praktycznych przykładów w EF:

Procedura zwracająca kolekcję encji

Dla tabeli Customer mamy przykładową procedurę składowaną:

CREATE PROCEDURE dbo.GetCustomers
(@Company VARCHAR(50), @ContactTitle VARCHAR(50))
AS
 BEGIN
  SELECT * FROM Customer
  WHERE (@Company IS null OR Company = @Company) AND
  (@ContactTitle is null or ContactTitle = @ContactTitle)
 END

Po dodaniu do EDM odpowiednich encji i procedury GetCustomers, przechodzimy do okna designera. Z menu dostępnego pod prawym przyciskiem myszy wybieramy opcję Add -> Function Import...
W menu należy podać nazwę procedury, oraz określić co ma zwracać ( w tym przypadku kolekcję encji typu Customer). To wszystko, teraz w kodzie możemy na obiekcie kontekstu wołać GetCustomers.

var allCustomers = context.GetCustomers("GoShopNow.com", "Sales Manager");
foreach (var customer in allCustomers)
{
    Console.WriteLine("\t{0}", customer.Name);
}

Procedura zwracająca wartości przez parametr

Przykładowa procedura przyjmująca jeden parametr wejściowy i dwa wyjściowe, zwracająca dodatkowo kolekcję encji.

CREATE PROCEDURE dbo.GetVehiclesWithRentals
(@date DATE,
@totalRentals INT OUTPUT,
@totalPayments DECIMAL(18,2) OUTPUT)
AS
 BEGIN
 SELECT @totalRentals = COUNT(*), @totalPayments = SUM(Payment)
 FROM dbo.Rental
 WHERE RentalDate = @date
 
 SELECT DISTINCT v.*
 FROM dbo.Vehicle AS v JOIN dbo.Rental AS r
 ON v.VehicleId = r.VehicleId 
END

Procedurę taką dodajemy w sposób analogiczny, jak poprzednio. Różnice pojawiają się w kodzie. Korzystamy z obiektów typu ObjectParameter

string reportDate = "2/2/2010";
var totalRentals = new ObjectParameter("totalRentals", typeof (int));
var totalPayments = new ObjectParameter("totalPayments", typeof(decimal));
var vehicles = context.GetVehiclesWIthRentals(DateTime.Parse(reportDate), 
    totalRentals, totalPayments);
foreach (var vehicle in vehicles)
{
    Console.WriteLine("{0} {1}, {2} year", vehicle.Manufacturer, vehicle.Model,
        vehicle.Year);
}
Console.WriteLine("Total Rentals: {0}", totalRentals.Value);
Console.WriteLine("Total Payments: {0}", totalPayments.Value);

Custom Functions

Custom Functions to funkcje definiowane na poziome storage model. Nie musimy zatem (być może nie mamy dostępu) tworzyć procedur na serwerze bazy danych, a możemy je dodać do SSDL. Zatem treść procedury dodajemy do schematu bazy <Schema></Schema> edytując plik.edmx jako XML. Na przykład można dodać taką procedurę:

<Function Name="MembersWithTheMostMessages" IsComposable="false">
  <CommandText>
    select m.*
    from dbo.Member m
    join
    (
    select distinct msg.MemberId
    from dbo.Message msg where DateSent = @datesent
    ) temp on m.MemberId = temp.MemberId
  </CommandText>
  <Parameter Name="datesent" Type="date" />
</Function>

Funkcję taką wykorzystuje się tam samo jak procedurę składowaną

var members = context.MemberWithTheMostMessages(today);

piątek, 28 grudnia 2012

[SQL|ORM] Entity Framework : Plain Old CLR Objects

Klasy generowane przez EF mają jedną zasadniczą wadę: są mocno związane z konkretnym EDM. Fakt ten ogranicza możliwości testowania i praktycznie eliminuje reużywalność. Jeżeli  chcemy skorzystać z własnych klas jako encji, mamy do dyspozycji mechanizm POCO. Nazwa Plain Old CLR oznacza mniej więcej tyle, że obiekty nie mają referencji do zewnętrznych frameworków, nie muszą dziedziczyć po żadnych specyficznych klasach.

Przykładowo, mamy zestaw tabel, mapowany przez EF na poniższy zestaw encji:


W pierwszej kolejności należy wyłączyć generowanie klas ustawiając w Designerze opcję Code Generation Strategy na None. Następnie tworzymy klasy dla encji. Każda nazwa oraz typ property (również navigation property) musi zgadzać się z nazwą z designera. Kolekcje reprezentujemy przez ISet<T>.

 public class Customer
    {
        public int CustomerId { get; set; }
        public string CustomerName { get; set; }
        public ISet<Order> Orders { get; set; }
        public Customer()
        {
            Orders = new HashSet<Order>();
        }
    }

    public class Order
    {
        public int OrderId { get; set; }
        public int CustomerId { get; set; }
        public DateTime OrderDate { get; set; }
        public Customer Customer { get; set; }
        public ISet<OrderDetail> OrderDetails { get; set; }
        public Order()
        {
            OrderDetails = new HashSet<OrderDetail>();
        }
    }

    public class OrderDetail
    {
        public int OrderId { get; set; }
        public int ProductId { get; set; }
        public decimal UnitPrice { get; set; }
        public int Quantitiy { get; set; }
        public Order Order { get; set; }
        public Product Product { get; set; }
    }

    public class Product
    {
        public int ProductId { get; set; }
        public string ProductName { get; set; }
        public decimal UnitPrice { get; set; }
    }

Ostatni krok to konieczność stworzenia klasy kontekstu.

public class EntityFrameworkRecipes : ObjectContext
{
    private ObjectSet<Customer> _customers;
    private ObjectSet<Order> _orders;
    private ObjectSet<OrderDetail> _orderdetails;
    private ObjectSet<Product> _products;

    public EntityFrameworkRecipes()
        : base("name=EntityFrameworkRecipesEntities2", "EntityFrameworkRecipesEntities2")
    {
        _orders = CreateObjectSet<Order>();
        _orderdetails = CreateObjectSet<OrderDetail>();
        _products = CreateObjectSet<Product>();
    }

    public ObjectSet<Customer> Customers
    {
        get { return _customers ?? (_customers = CreateObjectSet<Customer>()); }
    }

    public ObjectSet<Order> Orders
    {
        get { return _orders; }
    }

    public ObjectSet<OrderDetail> OrderDetails
    {
        get { return _orderdetails; }
    }

    public ObjectSet<Product> Products
    {
        get { return _products; }
    }
}

Do konstruktora bazowego przekazujemy ścieżkę do connectionstring z pliku konfiguracyjnego, oraz nazwę kontenera.

Wczytywanie encji powiązanych

Klasa ObjectContext udostępnie metodę LoadProperty i to z niej należy skorzystać, aby wczytać powiązane encje

context.LoadProperty(venue, v => v.Events);

Lazy Loading

Aby zapewnić Lazy Loading należy odpowiednie propercje w obiektach POCO oznaczyć słowem kluczowym virtual.  Dzięki temu stworzone zostaną obiekty proxy umożliwiające Lazy Loading. Dodatkowo na obiekcie kontekstu, w property ContextOptions należy ustawić opcję LazyLoadingEnabled na true.