Sdílet prostřednictvím


Migrace Code First

Migrace Code First představuje doporučený způsob, jak vyvíjet schéma databáze aplikace, pokud používáte pracovní postup Code First. Migrace obsahují sadu nástrojů, které umožňují:

  1. Vytvořit počáteční databázi, která spolupracuje s modelem EF
  2. Generovat migrace za účelem sledování změn prováděných v modelu EF
  3. Aktualizovat databázi na základě těchto změn

V následujícím návodu získáte přehled o migracích Code First v Entity Framework. Můžete si buď projít celý návod, nebo přeskočit na téma, které vás zajímá. Probírána jsou následující témata:

Sestavení počátečního modelu a databáze

Než začneme používat migrace, potřebujeme projekt a model Code First, s nímž budeme pracovat. Pro účely tohoto návodu použijeme kanonický model blogu a příspěvku.

  • Vytvořte novou konzolovou aplikaci MigrationsDemo
  • Přidání nejnovější verze balíčku NuGet EntityFramework do projektu
    • Nástroje –> Správce balíčků knihovny –> Konzola Správce balíčků
    • Spusťte příkaz Install-Package EntityFramework
  • Přidejte soubor Model.cs s kódem zobrazeným níže. Tento kód definuje jednu třídu Blog, která tvoří náš doménový model, a třídu BlogContext, která je naším kontextem EF Code First.
    using System.Data.Entity;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Data.Entity.Infrastructure;

    namespace MigrationsDemo
    {
        public class BlogContext : DbContext
        {
            public DbSet<Blog> Blogs { get; set; }
        }

        public class Blog
        {
            public int BlogId { get; set; }
            public string Name { get; set; }
        }
    }
  • Teď, když máme model, je čas ho použít k přístupu k datům. Aktualizujte soubor Program.cs níže uvedeným kódem.
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace MigrationsDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (var db = new BlogContext())
                {
                    db.Blogs.Add(new Blog { Name = "Another Blog " });
                    db.SaveChanges();

                    foreach (var blog in db.Blogs)
                    {
                        Console.WriteLine(blog.Name);
                    }
                }

                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
        }
    }
  • Spusťte aplikaci a uvidíte, že se vytvořila databáze MigrationsCodeDemo.BlogContext.

    Databáze LocalDB

Povolení migrací

Je čas provést s modelem několik dalších změn.

  • Pojďme ve třídě Blog použít vlastnost Url.
    public string Url { get; set; }

Pokud byste aplikaci spustili znovu, získali byste výjimku InvalidOperationException, která hlásí, že model, který zálohuje kontext BlogContext, se od vytvoření databáze změnil. Zvažte použití Migrace Code First k aktualizaci databáze ( http://go.microsoft.com/fwlink/?LinkId=238269).

Jak nám výjimka napovídá, je čas začít používat migrace Code First. Prvním krokem je povolit migrace pro náš kontext.

  • Spusťte příkaz Enable-Migrations v konzole Správce balíčků

    Tento příkaz přidal do našeho projektu složku Migrations. Ta obsahuje dva soubory:

  • Třída Konfigurace. Tato třída umožňuje nakonfigurovat chování migrací pro váš kontext. Pro účely tohoto návodu použijeme jen výchozí konfiguraci. Vzhledem k tomu, že projekt obsahuje jen jeden kontext Code First, příkaz Enable-Migrations automaticky vyplní typ kontextu, na který se tato konfigurace vztahuje.

  • Migrace InitialCreate. Tato migrace se vygenerovala, protože ještě před povolením migrací jsme nechali funkcí Code First vytvořit databázi. Kód v této vygenerované migraci představuje objekty, které již byly vytvořeny v databázi. V našem případě to je tabulka Blog se sloupci BlogId a Name. Název souboru obsahuje časové razítko, které usnadňuje řazení. Pokud by databáze ještě nebyla vytvořena, nebyla by migrace InitialCreate do projektu přidána. Místo toho by se při prvním volání Add-Migration vygenerovat do nové migrace kód pro vytvoření těchto tabulek.

Více modelů cílících na stejnou databázi

Při použití verzí starších než EF6 bylo možné použít k vygenerování a správě schématu databáze jen jeden model Code First. To bylo proto, že databáze obsahovala jen jednu tabulku __MigrationsHistory bez možnosti zjistit, které položky patří do kterého modelu.

Počínaje verzí EF6 obsahuje třída Configuration vlastnost ContextKey. Ta slouží jako jedinečný identifikátor pro každý model Code First. Odpovídající sloupec v tabulce __MigrationsHistory umožňuje sdílet tabulku položkám z více modelů. Ve výchozím nastavení je tato vlastnost nastavena na plně kvalifikovaný název kontextu.

Generování a spouštění migrací

Migrace Code First mají dva hlavní příkazy, se kterými se seznámíte.

  • Add-Migration vygeneruje další migraci na základě změn, které jste provedli v modelu od vytvoření poslední migrace
  • Update-Database použije všechny čekající migrace v databázi.

Potřebujeme vygenerovat migraci, abychom použili nově přidanou vlastnost Url. Příkaz Add-Migration nám umožňuje migrace pojmenovat, takže v našem případě to bude AddBlogUrl.

  • V konzole Správce balíčků spusťte příkaz Add-Migration AddBlogUrl.
  • Ve složce Migrations teď máme novou migraci AddBlogUrl. Název souboru migrace začíná časovým razítkem, které usnadňuje řazení.
    namespace MigrationsDemo.Migrations
    {
        using System;
        using System.Data.Entity.Migrations;

        public partial class AddBlogUrl : DbMigration
        {
            public override void Up()
            {
                AddColumn("dbo.Blogs", "Url", c => c.String());
            }

            public override void Down()
            {
                DropColumn("dbo.Blogs", "Url");
            }
        }
    }

Teď bychom mohli migraci upravit nebo doplnit, ale všechno vypadá dobře. Pomocí příkazu Update-Database použijeme tuto migraci v databázi.

  • V konzole Správce balíčků spusťte příkaz Update-Database.
  • Migrace Code First porovná migrace ve složce Migrations s těmi, které se použily v databázi. Bude potřeba použít migraci AddBlogUrl, takže ji spusťte.

Databáze MigrationsDemo.BlogContext se teď aktualizovala tak, že tabulka Blogs obsahuje sloupec Url.

Přizpůsobení migrací

Zatím jsme vygenerovali a spustili migraci bez provedení jakýchkoli změn. Teď se podíváme na úpravy kódu, který se generuje ve výchozím nastavení.

  • Je čas provést v našem modelu několik dalších změn, takže pojďme do třídy Blog přidat vlastnost Rating
    public int Rating { get; set; }
  • Pojďme také přidat novou třídu Post
    public class Post
    {
        public int PostId { get; set; }
        [MaxLength(200)]
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public Blog Blog { get; set; }
    }
  • Kromě toho přidáme do třídy Blog kolekci Posts, abychom mezi Blog a Post vytvořili druhý konec vztahu.
    public virtual List<Post> Posts { get; set; }

Pomocí příkazu Add-Migration necháme migrace Code First vygenerovat nejlepší možnou migraci. Tuto migraci nazveme AddPostClass.

  • V konzole Správce balíčků spusťte příkaz Add-Migration AddBlogUrl.

Migrace Code First si při generování těchto změn vedla docela dobře, ale je tu několik věcí, které budeme chtít změnit:

  1. Nejprve přidáme jedinečný index do sloupce Posts.Title (přidání do řádku 22 a 29 v následujícím kódu).
  2. Přidáváme také sloupec Blogs.Rating, který nemůže mít hodnotu null. Pokud tabulka obsahuje data, přiřadí se výchozí hodnotě CLR datového typu pro nový sloupec (Rating je celé číslo, takže v tomto případě 0). Chceme ale zadat výchozí hodnotu 3, aby stávající řádky v tabulce Blog začínaly obstojným hodnocením. (V kódu níže vidíte výchozí hodnotu na řádku 24.)
    namespace MigrationsDemo.Migrations
    {
        using System;
        using System.Data.Entity.Migrations;

        public partial class AddPostClass : DbMigration
        {
            public override void Up()
            {
                CreateTable(
                    "dbo.Posts",
                    c => new
                        {
                            PostId = c.Int(nullable: false, identity: true),
                            Title = c.String(maxLength: 200),
                            Content = c.String(),
                            BlogId = c.Int(nullable: false),
                        })
                    .PrimaryKey(t => t.PostId)
                    .ForeignKey("dbo.Blogs", t => t.BlogId, cascadeDelete: true)
                    .Index(t => t.BlogId)
                    .Index(p => p.Title, unique: true);

                AddColumn("dbo.Blogs", "Rating", c => c.Int(nullable: false, defaultValue: 3));
            }

            public override void Down()
            {
                DropIndex("dbo.Posts", new[] { "Title" });
                DropIndex("dbo.Posts", new[] { "BlogId" });
                DropForeignKey("dbo.Posts", "BlogId", "dbo.Blogs");
                DropColumn("dbo.Blogs", "Rating");
                DropTable("dbo.Posts");
            }
        }
    }

Naše upravená migrace je hotová, takže pojďme pomocí příkazu Update-Database aktualizovat databázi. Tentokrát specifikujeme příznak–Verbose, abyste viděli skript SQL, který migrace Code First spouští.

  • V konzole Správce balíčků spusťte příkaz Update-Database –Verbose.

Pohyb dat / vlastní SQL

Zatím jsme se věnovali operacím migrace, které nemění ani nepřesunují žádná data. Teď se ale pojďme na pohyb dat podívat blíže. Pro pohyby dat zatím není k dispozici žádná podpora, ale v našem skriptu můžeme kdykoliv spustit určité libovolné příkazy SQL.

  • Pojďme do modelu přidat vlastnost Post.Abstract. Později předvyplníme sloupec Abstract u stávajících příspěvků textem ze začátku sloupce Content.
    public string Abstract { get; set; }

Pomocí příkazu Add-Migration necháme migrace Code First vygenerovat nejlepší možnou migraci.

  • V konzole Správce balíčků spusťte příkaz Add-Migration AddPostAbstract.
  • Vygenerovaná migrace se postará o změny schématu. My ale také chceme předvyplnit sloupec Abstract prvními 100 znaky obsahu každého příspěvku. To uděláme tak, že po přidání sloupce přejdeme na skript SQL a spustíme příkaz UPDATE. (Přidán do řádku 12 v následujícím kódu.)
    namespace MigrationsDemo.Migrations
    {
        using System;
        using System.Data.Entity.Migrations;

        public partial class AddPostAbstract : DbMigration
        {
            public override void Up()
            {
                AddColumn("dbo.Posts", "Abstract", c => c.String());

                Sql("UPDATE dbo.Posts SET Abstract = LEFT(Content, 100) WHERE Abstract IS NULL");
            }

            public override void Down()
            {
                DropColumn("dbo.Posts", "Abstract");
            }
        }
    }

Naše upravená migrace vypadá dobře, takže pojďme pomocí příkazu Update-Database aktualizovat databázi. Specifikujeme příznak –Verbose, abychom viděli, jak se skript SQL spouští v databázi.

  • V konzole Správce balíčků spusťte příkaz Update-Database –Verbose.

Migrace na konkrétní verzi (včetně downgradu)

Zatím jsme vždy upgradovali na nejnovější migraci, ale může se stát, že budete chtít upgradovat nebo downgradovat na nějakou konkrétní migraci.

Řekněme, že chceme migrovat databázi do stavu, ve kterém byla po spuštění migrace AddBlogUrl. K tomu můžeme použít přepínač –TargetMigration.

  • V konzole Správce balíčků spusťte příkaz Update-Database –TargetMigration: AddBlogUrl.

Tento příkaz spustí skript Down pro migraci AddBlogAbstract a AddPostClass.

Pokud se chcete vrátit až na prázdnou databázi, můžete použít příkaz Update-Database –TargetMigration: $InitialDatabase.

Získání skriptu SQL

Pokud chce mít jiný vývojář tyto změny na svém počítači, stačí,když je jen synchronizuje, jakmile je zavedeme do správy zdrojového kódu. Když už naše nové migrace má, stačí spustit příkaz Update-Database a změny se projeví v místním prostředí. Pokud ale chceme tyto změny odeslat na testovací server a následně do produkčního prostředí, budeme potřebovat skript SQL, který předáme správci databáze.

  • Spusťte příkaz Update-Database, ale tentokrát zadejte příznak –Script, aby se změny nepoužily, ale zapsaly do skriptu. Specifikujeme také zdrojovou a cílovou migraci, pro níž se skript vygeneruje. Chceme, aby skript přešel z prázdné databáze ($InitialDatabase) na nejnovější verzi (migraceAddPostAbstract). Pokud cílovou migraci nezadáte, jako cíl se použije nejnovější migrace. Pokud nezadáte zdrojové migrace, použije se aktuální stav databáze.
  • V konzole Správce balíčků spusťte příkaz Update-Database -Script -SourceMigration: $InitialDatabase -TargetMigration: AddPostAbstract.

Migrace Code First spustí kanál migrace, ale namísto zavedení změn je zapíše do souboru .sql. Po vygenerování se skript otevře ve Visual Studiu, kde si ho můžete prohlédnout a uložit.

Generování idempotentních skriptů

Počínaje verzí EF6 platí, že pokud zadáte příkaz –SourceMigration $InitialDatabase, bude vygenerovaný skript idempotentní. Idempotentní skripty mohou jakoukoli verzi databáze upgradovat na nejnovější verzi (nebo specifikovanou verzi, pokud použijete –TargetMigration). Vygenerovaný skript obsahuje logiku pro kontrolu tabulky __MigrationsHistory a zavede pouze změny, které nebyly zavedeny dříve.

Automatický upgrade při spuštění aplikace (inicializační výraz MigrateDatabaseToLatestVersion)

Když nasazujete aplikaci, možná budete chtít, aby při spuštění automaticky upgradovala databázi (použily se všechny čekající migrace). To můžete provést registrací inicializačního výrazu databáze MigrateDatabaseToLatestVersion. Inicializační výraz databáze jednoduše obsahuje logiku, která se používá k zajištění správného nastavení databáze. Tato logika se spustí při prvním použití kontextu v procesu aplikace (AppDomain).

Na příkladu níže aktualizujeme soubor Program.cs tak, aby se nastavil inicializační výraz MigrateDatabaseToLatestVersion pro BlogContext ještě před použitím kontextu (řádek 14). Všimněte si, že také musíme přidat příkaz using pro obor názvů System.Data.Entity (řádek 5).

Když vytvoříme instanci tohoto inicializačního výrazu, musíme zadat typ kontextu (BlogContext) a konfiguraci migrací (Configuration) – konfigurace migrací je třída, která se přidala do složky Migrations, když jsme povolili migrace.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Data.Entity;
    using MigrationsDemo.Migrations;

    namespace MigrationsDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                Database.SetInitializer(new MigrateDatabaseToLatestVersion<BlogContext, Configuration>());

                using (var db = new BlogContext())
                {
                    db.Blogs.Add(new Blog { Name = "Another Blog " });
                    db.SaveChanges();

                    foreach (var blog in db.Blogs)
                    {
                        Console.WriteLine(blog.Name);
                    }
                }

                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
        }
    }

Jakmile se teď naše aplikace spustí, vždy nejprve zkontroluje, jestli cílí na aktuální databázi. Pokud aktuální není, použije všechny čekající migrace.