wtorek, 27 marca 2012

EF Code Migrations uruchomienie z kodu

Ostatnio dostałem zapytanie odnośnie uruchomienia migracji z kodu aplikacji. Jak wiadomo sama procedura standardowo jest przeprowadzana z PowerShell-a w VS.
Podstawową komendą jest Update-Database. Co dzieje się dalej?
Otóż jeżeli zobaczymy do katalogu gdzie znajduje się paczka z EF znajdziemy tam narzędzie migrate.exe:


Jeżeli uruchomimy narzędzie z linii komend zobaczymy następujący ekran:


Mamy tutaj ładne podsumowanie poleceń, których działanie pokazywałem w ostatnich postach odnośnie migracji.
W tym miejscu można by wywołać aplikację z odpowiednimi parametrami i otrzymać rezultat o który nam chodzi.
Istnieje także drugie rozwiązanie - dużo prostsze i lepsze...


Uruchomienie migracji bezpośrednio z kodu:
Team EF przygotował bardzo dobrze migracje aby można je było dokonywać z kodu. Otóż aby dokonać migracji wystarczą właściwie trzy linijki kodu (dla upartego można by scalić wszystko do jednej linijki):

            var configuration = new Configuration();
            var migrator = new DbMigrator(configuration);
            migrator.Update("AddBirthDateToPerson");

W podobny sposób można pobrać skrypt SQL:


            var configuration = new Configuration();
            var migrator = new DbMigrator(configuration);
            var scriptor = new MigratorScriptingDecorator(migrator);
            string script = scriptor.ScriptUpdate(null, "AddBirthDateToPerson");

poniedziałek, 19 marca 2012

Entity Framework - generowany SQL przez LINQ oraz operator IN w LINQ

Dziś krótko o dwóch przydatnych czynnościach, z którymi możemy się zmierzyć w przypadku EF + LINQ.


1. Pobieranie SQL-a generowanego przez LINQ
Pracując z EF nie wiele zwraca się uwagi na generowane zapytania przez ORM. Warto jednak niekiedy podglądnąć w jaki sposób jest generowane zapytanie do bazy. Sposób który podam może także przydać się w przypadku kiedy chcemy przeprowadzić optymalizację zapytań. Aby zobaczyć wygenerowane zapytanie do bazy należy skorzystać z poniższego kodu:


Code:
using System;
using System.Data.Objects;
using System.Linq;

namespace ConsoleApplication11
{
    class Program
    {
        static void Main()
        {
            using (var contex = new AdventureWorksLT2008R2Entities())
            {
                var customer = from c in contex.Customers
                               join soh in contex.SalesOrderHeaders on c.CustomerID equals soh.CustomerID
                               join sod in contex.SalesOrderDetails on soh.SalesOrderID equals sod.SalesOrderID
                               join p in contex.Products on sod.ProductID equals p.ProductID
                               where c.CustomerID == 30113
                               select p;
                Console.WriteLine(((ObjectQuery) customer).ToTraceString());
            }
        }
    }
}

W wyniku wywołania otrzymamy następujący wynik:


Otrzymany wynik możemy bez przeszkód przekleić do ManagementStudio i wywołać.



2. Operator IN w LINQ
Przez dłuższy czas szukałem sposoby w jaki generować za pomocą LINQ zapytania do bazy z użyciem operatora IN. W końcu po przegrzebaniu internetu znalazłem prostą odpowiedź:


Code:
using System;
using System.Data.Objects;
using System.Linq;

namespace ConsoleApplication11
{
    class Program
    {
        static void Main()
        {
            using (var contex = new AdventureWorksLT2008R2Entities())
            {
                int[] customerIds = {30046, 30047, 30048, 30050, 30051, 30052};
                var customer = from c in contex.Customers
                               join soh in contex.SalesOrderHeaders on c.CustomerID equals soh.CustomerID
                               join sod in contex.SalesOrderDetails on soh.SalesOrderID equals sod.SalesOrderID
                               join p in contex.Products on sod.ProductID equals p.ProductID
                               where customerIds.Contains(c.CustomerID)
                               select p;
                string sql = ((ObjectQuery) customer).ToTraceString();
                Console.WriteLine(sql);
            }
        }
    }
}

Tak więc:
  1. Definiuje tablicę z wartościami które mają trafić do zapytania
  2. W zapytaniu sprawdzam w tablicy czy nie zawiera wartości z bazy danych
W wyniku tego zapytania otrzymuję to co chciałem czyli operator IN:

SELECT
[Extent3].[ProductID] AS [ProductID],
[Extent3].[Name] AS [Name],
[Extent3].[ProductNumber] AS [ProductNumber],
[Extent3].[Color] AS [Color],
[Extent3].[StandardCost] AS [StandardCost],
[Extent3].[ListPrice] AS [ListPrice],
[Extent3].[Size] AS [Size],
[Extent3].[Weight] AS [Weight],
[Extent3].[ProductCategoryID] AS [ProductCategoryID],
[Extent3].[ProductModelID] AS [ProductModelID],
[Extent3].[SellStartDate] AS [SellStartDate],
[Extent3].[SellEndDate] AS [SellEndDate],
[Extent3].[DiscontinuedDate] AS [DiscontinuedDate],
[Extent3].[ThumbNailPhoto] AS [ThumbNailPhoto],
[Extent3].[ThumbnailPhotoFileName] AS [ThumbnailPhotoFileName],
[Extent3].[rowguid] AS [rowguid],
[Extent3].[ModifiedDate] AS [ModifiedDate]
FROM   [SalesLT].[SalesOrderHeader] AS [Extent1]
INNER JOIN [SalesLT].[SalesOrderDetail] AS [Extent2] ON [Extent1].[SalesOrderID] = [Extent2].[SalesOrderID]
INNER JOIN [SalesLT].[Product] AS [Extent3] ON [Extent2].[ProductID] = [Extent3].[ProductID]
WHERE [Extent1].[CustomerID] IN (30046,30047,30048,30050,30051,30052)

sobota, 17 marca 2012

Entity Framework Code First - atrybut ConcurrencyCheck

W ostatnim poście opisałem listę atrybutów z których możemy skorzystać w EF. Tutaj chciałbym dokończyć ten artykuł opisując bardziej szczegółowo jeden z ciekawszych moim zdaniem atrybutów - ConcurrencyCheck.
Zadaniem tego atrybutu jest sprawdzenie, czy podczas obecnego zapisu nie została dokonana w międzyczasie zmiana bezpośrednio w bazie danych na danym polu.

Zobaczmy na przykład.
W bazie w tabeli Person mamy następujący rekord:


Teraz napiszemy kod pozwalający pobrać wartość rekordu za pomocą EF:


Kod:

Code:
using System;
using System.Configuration;
using System.Linq;

namespace EF_CodeFirst
{
    class Program
    {
        static void Main()
        {
            var connectionString = ConfigurationManager.ConnectionStrings["MyDatabaseConnectionString"].ConnectionString;

            using (var context = new DatabaseContext(connectionString))
            {
                var userInDb = context.Persons.First();
                Console.WriteLine(userInDb);
            }
        }
    }
}

Dodajmy jeszcze teraz następujące dwie linijki kodu:


Code:
using System;
using System.Configuration;
using System.Linq;

namespace EF_CodeFirst
{
    class Program
    {
        static void Main()
        {
            var connectionString = ConfigurationManager.ConnectionStrings["MyDatabaseConnectionString"].ConnectionString;

            using (var context = new DatabaseContext(connectionString))
            {
                var userInDb = context.Persons.First();
                Console.WriteLine(userInDb);
                userInDb.FirstName = "Maciek";
                context.SaveChanges();
            }
        }
    }
}

Tak więc to co dodaliśmy powoduje zmianę imienia na Maciek oraz zapisanie zmian do bazy.

Przeprowadzimy teraz następują próbę:
1. ustawimy break point na linijkę z zapisem zmian do bazy danych.
2. Uruchomimy aplikację
3. Za pomocą np. Management Studio zmienimy wartość imienia na np. aaa
4. Wznowimy działanie programu.

Jaki będzie wynik tych operacji? Oczywiście wszystko się powiedzie. Mimo iż zmieniliśmy imię, nie do końca znając wartość w bazie danych, EF nie zaprotestował i pozwolił nam na to.
Niesie to za sobą niebezpieczeństwo tego iż, jeżeli będziemy zmieniać komuś np. NIP a w tym czasie ktoś inny na innym stanowisku zrobi to samo, nieumyślnie nadpiszemy jego zmiany.

Dlatego powstał atrybut  ConcurrencyCheck, który w momencie zapisu, uda się jeszcze raz do bazy i sprawdzi czy na danej krotce nie dokonano zmian.
Nałóżmy więc ten atrybut na nasze testowe pole:


Code:
using System.ComponentModel.DataAnnotations;

namespace EF_CodeFirst
{
    public class Person
    {
        [Key]
        [Column("Id")]
        public int IdPerson { get; set; }

        [StringLength(40)]
        [ConcurrencyCheck]
        public string FirstName { get; set; }

        [StringLength(40)]
        [Required]
        public string LastName { get; set; }
        public int BirthYear { get; set; }

        [StringLength(256)]
        [Required]
        public string Street { get; set; }

        public bool SalesRepresentative { get; set; }
        public CompanyCar CompanyCar { get; set; }

        public override string ToString()
        {
            return string.Format("{0} {1} {2} {3} {4}", IdPerson, FirstName, LastName, BirthYear, Street);
        }
    }
}

W naszym scenariuszu po próbie zapisania zmian zostanie wyrzucony następujący wyjątek:


Tak więc mamy świetne rozwiązanie bardzo częstego problemu, które w dodatku sprowadza się do nałożenia jednego atrybutu.

Entity Framework Code First - Data Adnotations

Tym razem coś a atrybutach - po co są i co nam dają.
Weźmiemy na warsztat klasę nad którą pracowaliśmy przez kilka ostatnich postów:


Code:
namespace EF_CodeFirst
{
    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int BirthYear { get; set; }
        public string Street { get; set; }

        public bool SalesRepresentative { get; set; }
        public CompanyCar CompanyCar { get; set; }
    }
}

W bazie klasa ta jest zmapowana na następującą tabelę:


Zacznijmy od tego, iż kolumny przechowujące dane znakowe są typu NVARCHAR(MAX). Jak wiadomo na takich kolumnach nie da się nałożyć indeksów. W przyszłości aby poprawić szybkość wyszukiwania z pewnością będziemy chcieli je nałożyć. Z drugiej strony wiemy że 99% imion mieści się w około 40 znakach jak i nazwiska nie przekraczają tej granicy.
Dodatkowo chcielibyśmy, aby nazwisko i ulica były polami obowiązkowymi (NOT NULL). Obecnie do każdego pola oprócz klucza głównego można wprowadzić wartość NULL.
Kolejną zmianą jaką dokonamy będzie zmiana nazwy pola w klasie Id na IdPerson - w bazie jednak chcemy aby nadal pozostała nazwa Id.
Zobaczmy w jaki sposób za pomocą atrybutów można dokonać opisanych zmian:


Code:
using System.ComponentModel.DataAnnotations;

namespace EF_CodeFirst
{
    public class Person
    {
        [Key]
        [Column("Id")]
        public int IdPerson { get; set; }

        [StringLength(40)]
        public string FirstName { get; set; }

        [StringLength(40)]
        [Required]
        public string LastName { get; set; }
        public int BirthYear { get; set; }

        [StringLength(256)]
        [Required]
        public string Street { get; set; }

        public bool SalesRepresentative { get; set; }
        public CompanyCar CompanyCar { get; set; }

        public override string ToString()
        {
            return string.Format("{0} {1} {2} {3} {4}", IdPerson, FirstName, LastName, BirthYear, Street);
        }
    }
}

Następnie tworzymy migrację za pomocą polecenia które podałem już w poprzednim poście:

Add-Migration AddAtributesToPersonClass

Zostanie wygenerowana klasa zawierająca potrzebne dane do przeprowadzenia migracji. Jeżeli teraz uruchomilibyśmy proces uaktualniania struktury bazy danych, otrzymalibyśmy następujący błąd:


Błąd mówi wyraźnie, że ustawiona kolumna Street nie może zawierać pustych wartości - w bazie mamy jeden rekord, który w polu street ma właśnie wartość NULL.
Tak więc dopóki owego NULL-a się nie pozbędziemy, nie zaktualizujemy schematu bazy. Jak w takim razie poradzić sobie z taką sytuacją? Można skorzystać z własnych komend SQL, o których pisałem w poprzednim poście.
Uzupełniamy więc klasę definiującą przeprowadzane migracje o nasz SQL:


Code:
namespace EF_CodeFirst.Migrations
{
    using System.Data.Entity.Migrations;
    
    public partial class AddAtributesToPersonClass : DbMigration
    {
        public override void Up()
        {
            Sql("UPDATE People SET street='' WHERE STREET IS NULL");
            AlterColumn("People", "FirstName", c => c.String(maxLength: 40));
            AlterColumn("People", "LastName", c => c.String(nullable: false, maxLength: 40));
            AlterColumn("People", "Street", c => c.String(nullable: false, maxLength: 256));
        }
        
        public override void Down()
        {
            AlterColumn("People", "Street", c => c.String());
            AlterColumn("People", "LastName", c => c.String());
            AlterColumn("People", "FirstName", c => c.String());
        }
    }
}

Teraz po wywołaniu polecenia:
Update-Database

zmiany zostaną wprowadzone do bazy a sama baza będzie mieć następującą postać:


Zobaczmy pełną listę dostępnych atrybutów:
  • Key - określa klucz główny (primary key)
  • StringLength - określa długość dla pola znakowego
  • MaxLength - określa maksymalną długość dla pola tekstowego
  • MinLength - określa minimalną liczbę znaków zapisaną do pola znakowego
  • Required - określa że dana wartość jest wymagana
  • ConcurrencyCheck - sprawdza przed zapisem czy wartość nie została zmieniona (opiszę dokładnie o co chodzi w kolejnym poście)
  • Timestamp - pole wykorzystywane w przypadku sprawdzenia dostępu wielowątkowego
  • ComplexType - definiuje że typ posiada pola także w innej klasie
  • Column - definiuje dla danego pola w klasie nazwę kolumny w bazie, typ danych oraz kolejność w zakładanej tabeli
  • Table - nazwa tabeli oraz schemy
  • NotMapped - właściwość nie jest mapowana z tabeli
  • DatabaseGenerated - definiuje w jaki sposób jest generowana wartość dla danego pola (autoinkrementacja, wyliczalne)
  • ForeignKey, InverseProperty - definiują klucz obcy dla relacji
Obecnie jest to pełna dostępna lista atrybutów, z pewnością w przyszłości zostanie uaktualniona o kolejne.

niedziela, 11 marca 2012

Entity Framework Code First - Migrations - SQL oraz downgrade

W poprzednim poście opisałem w jaki sposób korzystać z bardziej zaawansowanych elementów mechanizmu Code Migrations. Tym razem trochę o własnych skryptach SQL, które mogą być uruchamiane w czasie procesu migracji oraz o zmianie wersji do konkretnej wersji (wcześniejszej lub późniejszej)

1. Wywołanie własnego SQLa
Pierwszym problemem którym się zajmiemy jest wywołanie własnego SQLa. Zdarza się często, iż dodajemy pole i jego wartość jest zależna od innego pola. Jeżeli w bazie do tej porty nie było żadnych rekordów sprawa jest prosta, w przypadku gdy już były dane chcielibyśmy zapewne aby ta specyficzna wartość po dodaniu nowej kolumny, dla istniejących już rekordów została obliczona.

Dla celów testowych dodamy abstrakcyjne pole do klasy CompanyCar - MarkYear:


Code:
namespace EF_CodeFirst
{
    public class CompanyCar
    {
        public int Id { get; set; }
        public string Mark { get; set; }
        public string Producer { get; set; }
        public int ProductionYear { get; set; }
        public string RegisterNumber { get; set; }
        public string MarkYear { get; set; }
    }
}

W polu tym będziemy chcieli umieścić informacje o Marce samochodu i jego roczniku. Generujemy więc tak jak to już pokazywałem migrację:


Code:
namespace EF_CodeFirst.Migrations
{
    using System.Data.Entity.Migrations;
    
    public partial class AddCarYearToCompanyCar : DbMigration
    {
        public override void Up()
        {
            AddColumn("CompanyCars", "MarkYear", c => c.String());
        }
        
        public override void Down()
        {
            DropColumn("CompanyCars", "MarkYear");
        }
    }
}

Teraz czas na dodanie naszego SQLa. RObimy to za pomocą funkcji Sql:


Code:
namespace EF_CodeFirst.Migrations
{
    using System.Data.Entity.Migrations;
    
    public partial class AddCarYearToCompanyCar : DbMigration
    {
        public override void Up()
        {
            AddColumn("CompanyCars", "MarkYear", c => c.String());
            Sql("UPDATE CompanyCars SET MarkYear = Mark + ' ' + ProductionYear");
        }
        
        public override void Down()
        {
            DropColumn("CompanyCars", "MarkYear");
        }
    }
}

Jak widać prosta i banalna sprawa.


2. Migracja do danej wersji
Czasami mamy ochotę na migrację do specyficznej wersji, bądź też migracja w dół czyli przywrócenie poprzedniego stanu (schematu) bazy danych.

Jeżeli chcielibyśmy wrócić stan naszej bazy do żadnego punktu wystarczy użyć komendy:
Update-Database –TargetMigration:"nazwa_migracji"
Migracja spowoduje cofnięcie zmian aż do migracji nazwa_migracji.

Jeżeli chcielibyśmy wrócić do stanu początkowego bazy, wystarczy użyć polecenia:
Update-Database –TargetMigration:$InitialDatabase 


3. Generowanie skryptu SQL
Pomimo tego iż wszyscy deweloperzy mogą wprowadzić zmiany po zakomitowaniu naszego kodu do SVN-a poprzez proste wywołanie komendy uaktualniającej bazę danych, istnieje czasami potrzeba wygenerowania pliku SQL zawierającego dokonywane modyfikacje.
Aby utworzyć skrypt stosuje się następujące polecenie:
Update-Database -Script -SourceMigration:"Od" -TargetMigration:"Do"
gdzie:
  • Od - oznacza od jakiej migracji mają zostać pobrane zmiany
  • Do - oznacza do której migracji ma skrypt dotyczyć
W przypadku, gdy nie wyspecyfikujemy drugiej części komendy, jako punkt końcowy zostanie wzięta ostatnia migracja.

Biorąc przykładowy projekt, który tworzyłem przez ostatnie posty, możemy stworzyć skrypt, który obejmie zmiany od stanu początkowego bazy danych aż do migracji o nazwie AddSalesRepresentativeToPerson_CompanyCarEntity
Wprowadzamy więc polecenie:
PM> Update-Database -Script -SourceMigration: $InitialDatabase -TargetMigration AddSalesRepresentativeToPerson_CompanyCarEntity

Po zakończeniu procesu zostanie wyświetlony skrypt SQL:



Tak oto zakończył się temat migracji w EF.
Kolejny post będzie dotyczył Fluent API oraz adnotacją.

sobota, 10 marca 2012

Entity Framework Code First, Migrations - bardziej złożony scenariusz

W ostatnim poście przedstawiłem w jaki sposób można wykorzystać migracje podczas dodawania nowego pola do encji.
Dziś pokażę, co oprócz prostego dodania pola do bazy potrafi Code-Based Migrations.

Do naszego modelu chcemy dołożyć encję reprezentującą samochód służbowy (CompanyCar). Do samochodów może być przypisanych wielu użytkowników.

Klasa samochód służbowy:


Code:
namespace EF_CodeFirst
{
    public class CompanyCar
    {
        public int Id { get; set; }
        public string Mark { get; set; }
        public string Producer { get; set; }
        public int ProductionYear { get; set; }
        public string RegisterNumber { get; set; }
    }
}

Do klasy użytkownika dodajemy relację do tabeli CompanyCar oraz dodatkowe pole SalesRepresentative (przedstawiciel handlowy):


Code:
namespace EF_CodeFirst
{
    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int BirthYear { get; set; }
        public string Street { get; set; }

        public bool SalesRepresentative { get; set; }
        public CompanyCar CompanyCar { get; set; }
    }
}

Następnie dodajemy kod pozwalający na komunikowanie się encji CompanyCar z bazą danych:


Code:
using System.Configuration;
using System.Data.Entity;

namespace EF_CodeFirst
{
    public class DatabaseContext : DbContext
    {
        public DatabaseContext(string connectionString)
            : base(connectionString)
        {
        }

        public DatabaseContext()
            : base(ConfigurationManager.ConnectionStrings["MyDatabaseConnectionString"].ConnectionString)
        {
            
        }

        public DbSet<Person> Persons { get; set; }
        public DbSet<CompanyCar> CompanyCars { get; set; } 
    }
}

Podobnie jak w poprzednim poście stworzymy nową migrację:
PM> Add-Migration AddSalesRepresentativeToPerson_CompanyCarEntity


Została wygenerowana następująca klasa:


Code:
namespace EF_CodeFirst.Migrations
{
    using System.Data.Entity.Migrations;
    
    public partial class AddSalesRepresentativeToPerson_CompanyCarEntity : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                "CompanyCars",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                        Mark = c.String(),
                        Producer = c.String(),
                        ProductionYear = c.Int(nullable: false),
                        RegistrationNumber = c.String(nullable: false),
                    })
                .PrimaryKey(t => t.Id);
            
            AddColumn("People", "SalesRepresentative", c => c.Boolean(nullable: false));
            AddColumn("People", "CompanyCar_Id", c => c.Int());
            AddForeignKey("People", "CompanyCar_Id", "CompanyCars", "Id");
            CreateIndex("People", "CompanyCar_Id");
        }
        
        public override void Down()
        {
            DropIndex("People", new[] { "CompanyCar_Id" });
            DropForeignKey("People", "CompanyCar_Id", "CompanyCars");
            DropColumn("People", "CompanyCar_Id");
            DropColumn("People", "SalesRepresentative");
            DropTable("CompanyCars");
        }
    }
}

Dodajmy następujące zmiany:
  • kolumna SalesRepresentative będzie NOT NULL, oraz domyślana jej wartość będzie false
  • pole RegistrationNumber będzie NOT NULL, maksymalnie 10 znaków oraz unikalne w całej tabeli
 Zmiany które należy wprowadzić do klasy aby uzyskać taki efekt są następujące:


Code:
namespace EF_CodeFirst.Migrations
{
    using System.Data.Entity.Migrations;
    
    public partial class AddSalesRepresentativeToPerson_CompanyCarEntity : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                "CompanyCars",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                        Mark = c.String(),
                        Producer = c.String(),
                        ProductionYear = c.Int(nullable: false),
                        RegisterNumber = c.String(nullable: false)
                    })
                .PrimaryKey(t => t.Id)
                .Index(u => u.RegisterNumber, unique: true);
            
            AddColumn("People", "SalesRepresentative", c => c.Boolean(nullable: false));
            AddColumn("People", "CompanyCar_Id", c => c.Int());
            AddForeignKey("People", "CompanyCar_Id", "CompanyCars", "Id");
            CreateIndex("People", "CompanyCar_Id");
        }
        
        public override void Down()
        {
            DropIndex("People", new[] { "CompanyCar_Id" });
            DropForeignKey("People", "CompanyCar_Id", "CompanyCars");
            DropColumn("People", "CompanyCar_Id");
            DropColumn("People", "SalesRepresentative");
            DropTable("CompanyCars");
        }
    }
}

Ostatnią czynnością którą należy zrobić - uaktualnienie bazy danych:
PM> Update-Database


Jak widać do bazy zostały dodane odpowiednie pola, żądanego typu oraz odpowiedniej długości.

Entity Framework Code First Code Migrations

W ostatnim poście wspomniałem o nowości zwanej Code Migrations. Pozwala ona przede wszystkim na inkrementalne rozszerzanie bazy danych bez konieczności jej ponownego usuwania i tworzenia.

Wróćmy do naszego przykładu związanego z klasą Person. Do klasy tej dodaliśmy pole Street:


Code:
    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int BirthYear { get; set; }
        public string Street { get; set; }
    }

co spowodowało rzucenie wyjątku mówiącego o niekompatybilnych modelach danych.
Spróbujmy więc - używając Code Migrations uaktualnić bazę bez konieczności jej rekreacji.

W pierwszej kolejności dodałem domyślny konstruktor do Contextu:


Code:
using System.Configuration;
using System.Data.Entity;

namespace EF_CodeFirst
{
    public class DatabaseContext : DbContext
    {
        public DatabaseContext(string connectionString)
            : base(connectionString)
        {
        }

        public DatabaseContext()
            : base(ConfigurationManager.ConnectionStrings["MyDatabaseConnectionString"].ConnectionString)
        {
            
        }

        public DbSet<Person> Persons { get; set; }
    }
}

Następnie w oknie paczek wprowadzamy komendę:
PM> Enable-Migrations

Po pomyślnej instalacji w solucji zostanie dodany nowy folder wraz z plikami .cs:


Znaczenie poszczególnych plików:
  • Configuration - definiuje jak migracja wpływa na Context.
  • InitialCreate - ponieważ istnieje już stworzona baza danych, plik został wygenerowany i zawiera dane odnośnie obecnej postaci bazy danych.
Configuration.cs:


Code:
namespace EF_CodeFirst.Migrations
{
    using System.Data.Entity.Migrations;

    internal sealed class Configuration : DbMigrationsConfiguration<DatabaseContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(DatabaseContext context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
            //  to avoid creating duplicate seed data. E.g.
            //
            //    context.People.AddOrUpdate(
            //      p => p.FullName,
            //      new Person { FullName = "Andrew Peters" },
            //      new Person { FullName = "Brice Lambson" },
            //      new Person { FullName = "Rowan Miller" }
            //    );
            //
        }
    }
}

InitialCreate.cs:


Code:
namespace EF_CodeFirst.Migrations
{
    using System.Data.Entity.Migrations;
    
    public partial class InitialCreate : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                "People",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                        FirstName = c.String(),
                        LastName = c.String(),
                        BirthYear = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.Id);
            
        }
        
        public override void Down()
        {
            DropTable("People");
        }
    }
}

Komendy których będziemy używać w przypadku migracji:
  • Add-Migration - zawiera zmiany dokonane na modelu
  • Update-Database - uaktualnia bazę danych, wprowadzając dokonane zmiany
Aby dodać migrację dla pola Street wprowadzamy następujące polecenie w Package Manager:
PM> Add-Migration AddStreetToPersonClass


Została wygenerowana w tym momencie nowa klasa:


Code:
namespace EF_CodeFirst.Migrations
{
    using System.Data.Entity.Migrations;
    
    public partial class AddStreetToPersonClass : DbMigration
    {
        public override void Up()
        {
            AddColumn("People", "Street", c => c.String());
        }
        
        public override void Down()
        {
            DropColumn("People", "Street");
        }
    }
}

Następnie aby użyć stworzonej migracji oraz dodać pole do bazy wprowadzamy polecenie:
PM> Update-Database


Po zakończeniu procesu możemy zobaczyć, iż zmiany zostały wprowadzone do bazy danych:


Zauważyć można, iż dane pozostały w takiej formie jak przed dodaniem kolumny.

piątek, 9 marca 2012

Entity Framework Code First

W tym poście zostanie omówiony model CodeFirst dla EF.
Zaczniemy od instalacji EF za pomocą konsoli NuGet:
W konsoli wpisujemy następujące polecenie:
Install-Package EntityFramework






Po instalacji referencje do odpowiedniej biblioteki dll zostaną automatycznie dodane do projektu.

Następnym krokiem będzie zdefiniowanie naszego modelu. W tym celu stworzymy prostą klasę przechowującą informację o użytkowniku:


Code:
    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int BirthYear { get; set; }
    }

Następnym krokiem jest stworzenie Contextu, za pomocą którego będziemy porozumiewali się z bazą danych:


Code:
    public class DatabaseContext : DbContext
    {
        public DbSet<Person> Persons { get; set; }
    }

Następnie spróbujmy wykorzystać stworzony przez nas Context aby dodać nową osobę do bazy danych:


Code:
namespace EF_CodeFirst
{
    class Program
    {
        static void Main()
        {
            using (var context = new DatabaseContext())
            {
                context.Persons.Add(new Person{BirthYear = 2000, FirstName = "Jan", LastName = "Kowalski"});
                context.SaveChanges();
            }
        }
    }
}

Jeżeli zobaczymy poniższy wyjątek:


Oznacza to, iż provider ma problem z odnalezieniem serwera bazy danych. Rozwiązaniem tego problemu jest jawne podanie connection stringa do naszej bazy danych (w większości przypadków będziemy tak postępowali):

1. Do klasy dziedziczącej z DbContextu dodajemy konstruktor przyjmujący ConnectionString:


Code:
using System.Data.Entity;

namespace EF_CodeFirst
{
    public class DatabaseContext : DbContext
    {
        public DatabaseContext(string connectionString)
            : base(connectionString)
        {
        }

        public DbSet<Person> Persons { get; set; }
    }
}


2. W App/Web configu podajemy odpowiedni connectionString:


Code:
  <connectionStrings>
    <add connectionString="Data Source=localhost;Initial Catalog=MyDatabase;Integrated Security=SSPI;" 
         name="MyDatabaseConnectionString"
         providerName="System.Data.SqlClient"/>
  </connectionStrings>

3. Zmieniamy kod wykorzystujący DbContext:


Code:
using System.Configuration;

namespace EF_CodeFirst
{
    class Program
    {
        static void Main()
        {
            var connectionString = ConfigurationManager.ConnectionStrings["MyDatabaseConnectionString"].ConnectionString;

            using (var context = new DatabaseContext(connectionString))
            {
                context.Persons.Add(new Person{BirthYear = 2000, FirstName = "Jan", LastName = "Kowalski"});
                context.SaveChanges();
            }
        }
    }
}

Teraz, po uruchomieniu programu możemy zobaczyć co się stało na serwerze bazo danowym. Za pomocą MS możemy zobaczyć iż została stworzona z automatu nowa baza danych z odpowiednią tabelą, oraz dodany rekord:


Jak widać nie napisaliśmy ani jednej linijki kodu SQL, a baza danych została wygenerowana.

Spróbujmy teraz dodać nowe pole (street) do klasy reprezentującej naszą osobę:


Code:
    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int BirthYear { get; set; }
        public string Street { get; set; }
    }

Po ponownym uruchomieniu programu otrzymamy następujący wyjątek:


Link podany w treści wyjątku prowadzi do strony gdzie opisano nowość w EF - code migrations.
O tej nowości napiszę w kolejnym poście związanym z EF Code First.

czwartek, 8 marca 2012

Entity Framework na przestrzeni wydań

Można powiedzieć, że Entity Framework przeżywa w ostatnim czasie renesans. Team który zajmuje się wdrażaniem nowych wersji wydaje kolejne wersje które zawierają wiele usprawnień, ulepszeń i oczywiście optymalizacji.
W gąszczu tych nowości można się poczuć zagubionym. W tym artykule postaram się przybliżyć co nowego dzieje się w popularnym ORMie.

Ogromną zaletą EF nad LINQ2SQL jest z pewnością wsparcie dla znakomitej większości baz danych. Listę gotowych providerów można znaleźć pod adresem: http://msdn.microsoft.com/en-us/data/dd363565.aspx.

EF pojawił się w świecie .NET wraz z wersją 3.5 SP1 (VS 2008). Wersja ta była na tyle uboga, iż oferowała jedynie podstawowe możliwości ORM takie jak generowanie schematu klas na podstawie bazy danych oraz podstawowe operacje na encjach.

Wersja 4.0 wyszła wraz z VS 2010. Wersja ta zawierała wiele ulepszeń oraz nowości, z których można wymienić takie jak:
  • Model First - na podstawie zaprojektowanych klas w narzędziu graficznym jest generowana baza danych
  • POCO - nie ma potrzeby dziedziczenia po klasie EntityObject
  • Lazy Loading - pobieranie danych w tle (gdy są potrzebne)
  • Code generators - możliwość dostosowania sposobu w jaki są generowane klasy
Następnym wydaniem był EF 4.1. Dodano w nim takie nowości jak:
  • możliwość instalacji poprzez konsolę NuGet
  • lekki obiekt DbContext
W kolejnym wydaniu (4.2) team odpowiedzialny za EF zajął się głównie poprawą znalezionych błędów przez deweloperów.

Wersja 4.3 wprowadza wiele nowości:
  • inkrementalna rozbudowa schematu bazy danych na podstawie definicji POCO
  • usunięcie tabeli EdmMetadata 
  • uwzględnienie atrybutów na polach typu private oraz protected
  • zwiększenie ilości parametrów konfiguracyjnych które można umieścić w pliku App/Web config
 Wersje EF: obecnie mamy do czynienia z czterema modelami podejścia do EF:
  • Model First
  • Database First
  • Code First (new database)
  • Code First (exist database)
Zobaczmy na bardzo przydatny diagram:


Wynika z niego iż w przypadku nowej bazy danych prawidłowymi wyborami są:
  • Model First - stworzenie modelu w narzędziu graficznym, wygenerowanie bazy na podstawie takiego diagramu
  • Code First - zdefiniowanie mapowania w kodzie, wygenerowanie bazy podczas uruchomienia bazy na podstawie stworzonego kodu
W przypadku istniejących baz danych warto wybrać podejścia:
  • Database First - stworzenie diagramu EDMX przy użyciu inżynierii wstecznej, wygenerowanie klas na podstawie stworzonego schematu
  • Code First - definicja klas i mapowania w kodzie (dostępne są też narzędzia usprawniające to zadanie)
W kolejnych postach przedstawię w jaki sposób korzystać z przedstawionych modeli.

niedziela, 4 marca 2012

Memory leaks w Prism

Każda technologia ma swoje zalety i wady. Prism także nie jest pozbawiony wad. Jedną z bardziej widocznych wad jest wycieka pamięci podczas stosowania podregionów. Memory leak, czyli sytuacja w której obiekt jest przechowywany w pamięci, ale nie można się do niego odwołać jest z pewnością jedną z największych bolączek programistycznych. W tym poście postaram się wyjaśnić gdzie znajduje się owy wyciek oraz na podstawie wpisu na blogu Damian Schenkelman-a pokaże w jaki sposób można ominąć ten problem.
Cały problem można znaleźć oficjalnie opisany pod tym adresem.

Podany jest tam także przykład aplikacji, w którym widać efekt działania memory leak-a. Mamy następującą aplikację:


Całość jest zrozumiała i prosta. Po kliknięciu na przycisk Remove MainView from MainRegion oczekujemy, że główny widok wraz z jego pod widokami zostanie usunięty. Drugi przycisk Check RegionManager regions count, sprawdza ile jest aktualnie zarejestrowanych widoków.
Zobaczmy co się stanie w momencie kiedy najpierw usuniemy widoki a następnie wyświetlimy ilość zarejestrowanych widoków:


Jak widać zarejestrowanych widoków jest 3. Powinien być jeden. Pozostałe dwa widoki to TopRightView oraz BottomRightView. Nie zostały usunięte automatycznie.

Rozwiązaniem jest dosyć proste. W momencie zmiany w RegionManager należy przejść po wszystkich widokach regionu i usunąć je.
Realizuje je następująca klasa:


Code:
    public class ClearChildViewsRegionBehavior : RegionBehavior
    {
        public const string BehaviorKey = "ClearChildViews";


        protected override void OnAttach()
        {
            Region.PropertyChanged += RegionPropertyChanged;
        }

        void RegionPropertyChanged(object sender, System.ComponentModelPropertyChangedEventArgs e)
        {
            if (e.PropertyName != "RegionManager") return;
            if (Region.RegionManager != null) return;
            foreach (var dependencyObject in Region.Views.OfType<DependencyObject>())
            {
                dependencyObject.ClearValue(RegionManager.RegionManagerProperty);
            }
        }
    }

Następnie zostaje jeszcze podpięcie klasy w Bootstrapperze:


Code:
        protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
        {
            var regionBehaviorTypesDictionary = base.ConfigureDefaultRegionBehaviors();
            regionBehaviorTypesDictionary.AddIfMissing
                (ClearChildViewsRegionBehavior.BehaviorKey, typeof(ClearChildViewsRegionBehavior));
            return regionBehaviorTypesDictionary;
        }

Teraz po wykonaniu wcześniej opisanego usunięcia i zliczeniu ilości widoków w regionie powinniśmy uzyskać następujący wynik: