スキャフォールディング (リバース エンジニアリング)

リバース エンジニアリングは、データベース スキーマに基づいてエンティティ型クラスと DbContext クラスをスキャフォールディングするプロセスです。 これは、EF Core パッケージ マネージャー コンソール (PMC) ツールの Scaffold-DbContext コマンド、または .NET コマンド ライン インターフェイス (CLI) ツールの dotnet ef dbcontext scaffold コマンドを使用して実行できます。

Note

ここで説明されている DbContext 型とエンティティ型のスキャフォールディングは、Visual Studio を使った ASP.NET Core でのコントローラーのスキャフォールディング (ここでは説明されていません) とは異なるものです。

ヒント

Visual Studio をお使いの場合は、EF Core Power Tools コミュニティ拡張機能を試してください。 これらのツールに含まれるグラフィカル ツールは、EF Core のコマンド ライン ツールを基に構築されており、ワークフローとカスタマイズの追加オプションを提供します。

前提条件

  • スキャフォールディングを行う前に、Visual Studio でのみ動作する PMC ツール、または .NET でサポートされるすべてのプラットフォームで動作する .NET CLI ツールを、インストールする必要があります。
  • スキャフォールディングするプロジェクトに Microsoft.EntityFrameworkCore.Design の NuGet パッケージをインストールします。
  • スキャフォールディングの基にするデータベース スキーマを対象とするデータベース プロバイダー用の NuGet パッケージをインストールします。

必須の引数

PMC と .NET CLI のどちらのコマンドにも、データベースへの接続文字列と、使用する EF Core データベース プロバイダーを指定する、2 つの必須引数があります。

接続文字列

コマンドの最初の引数は、データベースへの接続文字列です。 ツールでは、この接続文字列を使用してデータベース スキーマを読み取ります。

接続文字列を引用符で囲む方法とエスケープする方法は、コマンドの実行に使うシェルによって異なります。詳しくは、シェルのドキュメントをご覧ください。 たとえば、PowerShell では $ 文字をエスケープする必要がありますが、\ については不要です。

次の例では、Microsoft.EntityFrameworkCore.SqlServer データベース プロバイダーを利用して、コンピューターの SQL Server LocalDB インスタンスにある Chinook データベースから、エンティティ型と DbContext をスキャフォールディングします。

dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook" Microsoft.EntityFrameworkCore.SqlServer

接続文字列のユーザー シークレット

ホスティング モデルと構成システム (ASP.NET Core プロジェクトなど) を使用する .NET アプリケーションがある場合は、Name=<connection-string> 構文を使って構成から接続文字列を読み取ることができます。

たとえば、次のような構成ファイルを使う ASP.NET Core アプリケーションについて考えます。

{
  "ConnectionStrings": {
    "Chinook": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Chinook"
  }
}

構成ファイル内のこの接続文字列を使い、次のようにして、データベースからスキャフォールディングできます。

dotnet ef dbcontext scaffold "Name=ConnectionStrings:Chinook" Microsoft.EntityFrameworkCore.SqlServer

ただし、接続文字列を構成ファイルに格納することは、たとえばソース管理へのプッシュなどにより、誤って簡単に公開されるため、お勧めできません。 代わりに、接続文字列は、Azure Key Vault を使ったり、ローカル環境で作業するときはシークレット マネージャー ツール ("ユーザー シークレット" とも呼ばれます) を使うなど、セキュリティで保護された方法で格納する必要があります。

たとえば、ユーザー シークレットを使うには、最初に ASP.NET Core の構成ファイルから接続文字列を削除します。 次に、ASP.NET Core プロジェクトと同じディレクトリで次のコマンドを実行して、ユーザー シークレットを初期化します。

dotnet user-secrets init

このコマンドは、ソース コードとは別にコンピューター上のストレージを設定し、このストレージ用のキーをプロジェクトに追加します。

次に、接続文字列をユーザー シークレットに格納します。 たとえば次のような点です。

dotnet user-secrets set ConnectionStrings:Chinook "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook"

その後、前は構成ファイルの名前付き接続文字列を使用したのと同じコマンドで、代わりにユーザー シークレットに格納されている接続文字列を使います。 たとえば次のような点です。

dotnet ef dbcontext scaffold "Name=ConnectionStrings:Chinook" Microsoft.EntityFrameworkCore.SqlServer

スキャフォールディングされたコード内の接続文字列

既定では、スキャフォールダーはスキャフォールディングされたコード内の接続文字列を含むようになりますが、警告が発生します。 たとえば次のような点です。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
    => optionsBuilder.UseSqlServer("Data Source=(LocalDb)\\MSSQLLocalDB;Database=AllTogetherNow");

これが行われるのは、生成されたコードが始めて使われるときにクラッシュしないようにするためですが、これにより学習エクスペリエンスが非常に低下します。 ただし、警告が示すように、運用コードには接続文字列が存在しないようにする必要があります。 接続文字列のさまざまな管理方法については、「DbContext の有効期間、構成、および初期化」をご覧ください。

ヒント

-NoOnConfiguring (Visual Studio PMC) または --no-onconfiguring (.NET CLI) オプションを渡すことで、接続文字列を含む OnConfiguring メソッドが作成されないようにできます。

プロバイダー名

2 番目の引数はプロバイダー名です。 プロバイダー名は通常、プロバイダーの NuGet パッケージ名と同じです。 たとえば、SQL Server や Azure SQL の場合は、Microsoft.EntityFrameworkCore.SqlServer を使います。

コマンド ライン オプション

スキャフォールディング プロセスは、さまざまなコマンド ライン オプションによって制御できます。

テーブルとビューの指定

既定では、データベース スキーマに含まれるすべてのテーブルとビューがエンティティ型にスキャフォールディングされます。 スキーマとテーブルを指定することにより、スキャフォールディングされるテーブルとビューを制限できます。

-Schemas (Visual Studio PMC) または --schema (.NET CLI) 引数では、エンティティ型を生成するテーブルとビューのスキーマを指定します。 この引数を省略すると、すべてのスキーマが含まれます。 このオプションを使うと、-Tables または --table を使って明示的に含められていないものも含めて、スキーマ内のすべてのテーブルとビューがモデルに含まれます。

-Tables (Visual Studio PMC) または --table (.NET CLI) 引数では、エンティティ型を生成するテーブルとビューを指定します。 特定のスキーマのテーブルまたはビューは、"schema.table" または "schema.view" の形式を使って含めることができます。 このオプションを省略すると、すべてのテーブルとビューが含まれます。 |

たとえば、ArtistsAlbums テーブルのみをスキャフォールディングするには:

dotnet ef dbcontext scaffold ... --table Artist --table Album

CustomerContractor スキーマのすべてのテーブルとビューをスキャフォールディングするには:

dotnet ef dbcontext scaffold ... --schema Customer --schema Contractor

たとえば、Customer スキーマの Purchases テーブルおよび Contractor スキーマの AccountsContracts テーブルをスキャフォールディングするには:

dotnet ef dbcontext scaffold ... --table Customer.Purchases --table Contractor.Accounts --table Contractor.Contracts

データベース名の保持

既定では、型およびプロパティについての .NET の名前付け規則に適切に一致するよう、テーブル名と列名が固定されています。 -UseDatabaseNames (Visual Studio PMC) または --use-database-names (.NET CLI) を指定すると、元のデータベース名を可能な限り保持するこの動作が無効になります。 無効な .NET 識別子は引き続き固定され、ナビゲーション プロパティのような合成された名前は .NET の名前付け規則に準拠したままになります。

たとえば、次のようなテーブルについて考えます。

CREATE TABLE [BLOGS] (
    [ID] int NOT NULL IDENTITY,
    [Blog_Name] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Blogs] PRIMARY KEY ([ID]));

CREATE TABLE [posts] (
    [id] int NOT NULL IDENTITY,
    [postTitle] nvarchar(max) NOT NULL,
    [post content] nvarchar(max) NOT NULL,
    [1 PublishedON] datetime2 NOT NULL,
    [2 DeletedON] datetime2 NULL,
    [BlogID] int NOT NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([id]),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogID]) REFERENCES [Blogs] ([ID]) ON DELETE CASCADE);

既定では、これらのテーブルからは次のエンティティ型がスキャフォールディングされます。

public partial class Blog
{
    public int Id { get; set; }
    public string BlogName { get; set; } = null!;
    public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}

public partial class Post
{
    public int Id { get; set; }
    public string PostTitle { get; set; } = null!;
    public string PostContent { get; set; } = null!;
    public DateTime _1PublishedOn { get; set; }
    public DateTime? _2DeletedOn { get; set; }
    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; } = null!;
    public virtual ICollection<Tag> Tags { get; set; } = new List<Tag>();
}

一方、-UseDatabaseNames または --use-database-names を使うと、結果のエンティティ型は次のようになります。

public partial class BLOG
{
    public int ID { get; set; }
    public string Blog_Name { get; set; } = null!;
    public virtual ICollection<post> posts { get; set; } = new List<post>();
}

public partial class post
{
    public int id { get; set; }
    public string postTitle { get; set; } = null!;
    public string post_content { get; set; } = null!;
    public DateTime _1_PublishedON { get; set; }
    public DateTime? _2_DeletedON { get; set; }
    public int BlogID { get; set; }
    public virtual BLOG Blog { get; set; } = null!;
}

マッピング属性 (データ注釈とも呼ばれる) を使用する

既定では、エンティティ型は OnModelCreatingModelBuilder API を使って構成されます。 可能な場合に代わりにマッピング属性を使うには、-DataAnnotations (PMC) または --data-annotations (.NET Core CLI) を指定します。

たとえば、Fluent API を使用すると、次のようにスキャフォールディングされます。

entity.Property(e => e.Title)
    .IsRequired()
    .HasMaxLength(160);

データ注釈を使用すると、次のようにスキャフォールディングされます。

[Required]
[StringLength(160)]
public string Title { get; set; }

ヒント

モデルの一部の側面は、マッピング属性を使って構成できません。 スキャフォールダーは、引き続きモデル構築 API を使ってこれらのケースを処理します。

DbContext 名

スキャフォールディングされた DbContext クラスの名前は、既定では、データベースの名前にサフィックス Context が付いたものになります。 別のものを指定するには、PMC では -Context を使用し、.NET Core CLI では --context を使用します。

ターゲットのディレクトリと名前空間

エンティティ クラスと DbContext クラスは、プロジェクトのルート ディレクトリにスキャフォールディングされ、プロジェクトの既定の名前空間が使用されます。

--output-dir を使用して、クラスがスキャフォールディングされるディレクトリを指定することができます。また、--context-dir を使用して、エンティティ型クラスとは別のディレクトリに DbContext クラスをスキャフォールディングすることができます。

dotnet ef dbcontext scaffold ... --context-dir Data --output-dir Models

既定では、名前空間はルート名前空間に、プロジェクトのルート ディレクトリの下にあるサブディレクトリの名前を加えたものになります。 ただし、--namespace を使用して、すべての出力クラスの名前空間をオーバーライドできます。 また、--context-namespace を使用して、DbContext クラスの名前空間のみをオーバーライドすることもできます。

dotnet ef dbcontext scaffold ... --namespace Your.Namespace --context-namespace Your.DbContext.Namespace

スキャフォールディングされたコード

既存のデータベースからのスキャフォールディングの結果は、次のようになります。

  • DbContext を継承するクラスを含むファイル
  • 各エンティティ型のファイル

ヒント

EF7 以降では、T4 テキスト テンプレートを使って生成されたコードをカスタマイズすることもできます。 詳しくは、「カスタム リバース エンジニアリング テンプレート」をご覧ください。

C# の null 許容参照型

スキャフォールダーは、C# の null 許容参照型 (NRT) を使う EF モデルとエンティティ型を作成できます。 NRT の使用法は、コードがスキャフォールディングされる C# プロジェクトで NRT のサポートが有効になっていると、自動的にスキャフォールディングされます。

たとえば、次の Tags テーブルには、null 許容と null 非許容の両方の文字列型の列が含まれています。

CREATE TABLE [Tags] (
  [Id] int NOT NULL IDENTITY,
  [Name] nvarchar(max) NOT NULL,
  [Description] nvarchar(max) NULL,
  CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));

この結果、生成されたクラスには、対応する null 許容と null 非許容の文字列プロパティがあります。

public partial class Tag
{
    public Tag()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public string? Description { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

同様に、次の Posts テーブルには、Blogs テーブルとの必要なリレーションシップが含まれています。

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NOT NULL,
    [Contents] nvarchar(max) NOT NULL,
    [PostedOn] datetime2 NOT NULL,
    [UpdatedOn] datetime2 NULL,
    [BlogId] int NOT NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([Id]));

これにより、ブログ間の null 非許容 (必須) リレーションシップがスキャフォールディングされます。

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

    public int Id { get; set; }
    public string Name { get; set; } = null!;

    public virtual ICollection<Post> Posts { get; set; }
}

そして Post は次のようになります。

public partial class Post
{
    public Post()
    {
        Tags = new HashSet<Tag>();
    }

    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public string Contents { get; set; } = null!;
    public DateTime PostedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public int BlogId { get; set; }

    public virtual Blog Blog { get; set; } = null!;

    public virtual ICollection<Tag> Tags { get; set; }
}

多対多のリレーションシップ

スキャフォールディング プロセスは、単純な結合テーブルを検出し、それらに対して多対多のマッピングを自動的に生成します。 たとえば、PostsTags のテーブルと、それらを結合する結合テーブル PostTag を考えてみます。

CREATE TABLE [Tags] (
  [Id] int NOT NULL IDENTITY,
  [Name] nvarchar(max) NOT NULL,
  [Description] nvarchar(max) NULL,
  CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NOT NULL,
    [Contents] nvarchar(max) NOT NULL,
    [PostedOn] datetime2 NOT NULL,
    [UpdatedOn] datetime2 NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]));

CREATE TABLE [PostTag] (
    [PostsId] int NOT NULL,
    [TagsId] int NOT NULL,
    CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]),
    CONSTRAINT [FK_PostTag_Posts_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tags] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_PostTag_Tags_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([Id]) ON DELETE CASCADE);

これをスキャフォールディングした結果は、Post のクラスになります。

public partial class Post
{
    public Post()
    {
        Tags = new HashSet<Tag>();
    }

    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public string Contents { get; set; } = null!;
    public DateTime PostedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public int BlogId { get; set; }

    public virtual Blog Blog { get; set; } = null!;

    public virtual ICollection<Tag> Tags { get; set; }
}

また、Tag のクラスは次のようになります。

public partial class Tag
{
    public Tag()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public string? Description { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

しかし、PostTag テーブルのクラスはありません。 代わりに、多対多リレーションシップの構成がスキャフォールディングされます。

entity.HasMany(d => d.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity<Dictionary<string, object>>(
        "PostTag",
        l => l.HasOne<Tag>().WithMany().HasForeignKey("PostsId"),
        r => r.HasOne<Post>().WithMany().HasForeignKey("TagsId"),
        j =>
            {
                j.HasKey("PostsId", "TagsId");
                j.ToTable("PostTag");
                j.HasIndex(new[] { "TagsId" }, "IX_PostTag_TagsId");
            });

他のプログラミング言語

Microsoft によって公開されている EF Core パッケージでは、C# コードのスキャフォールディングが行われます。 ただし、基になるスキャフォールディング システムでは、他の言語へのスキャフォールディング用のプラグイン モデルがサポートされています。 次のようなさまざまなコミュニティ実行プロジェクトでは、このプラグイン モデルが使われています。

コードのカスタマイズ

EF7 以降で、生成されたコードをカスタマイズする最適な方法の 1 つは、その生成に使われる T4 テンプレートをカスタマイズすることです。

生成後にコードを変更することもできますが、これを行う最善の方法は、データベース モデルの変更時にスキャフォールディング プロセスを再実行するかどうかによって異なります。

1 回のみスキャフォールディングする

この方法では、スキャフォールディングされたコードが、コード ベースのマッピングを進めるための起点になります。 生成されたコードに対して、どのような変更でも行うことができます。プロジェクト内の他のコードと同様に、通常のコードになります。

データベースと EF モデルの同期の維持は、次の 2 つの方法のいずれかで行うことができます。

  • EF Core データベースの移行を使うように切り替え、エンティティ型と EF モデルの構成を信頼できる情報源として使用し、移行を使ってスキーマを駆動します。
  • データベースが変更されたときは、エンティティ型と EF 構成を手動で更新します。 たとえば、新しい列がテーブルに追加された場合は、その列のプロパティをマップされたエンティティ型に追加し、マッピング属性や OnModelCreating のコードを使って必要な構成を追加します。 これは比較的簡単であり、唯一本当に課題となるのは、コードを担当する開発者が対応できるように、データベースの変更が何らかの方法で記録または検出されるようにするプロセスです。

スキャフォールディングの繰り返し

スキャフォールディングを 1 回だけ行うのではなく、データベースが変更されるたびに再スキャフォールディングする方法もあります。 これにより、以前にスキャフォールディングされたコードは "上書き" されます。つまり、そのコード内のエンティティ型または EF 構成に対して行われた変更は、すべて失われます。

[ヒント] 偶発的なコードの損失を防ぐため、既定では、EF コマンドによって既存のコードは上書きされません。 -Force (Visual Studio PMC) または --force (.NET CLI) 引数を使って、既存のファイルを強制的に上書きできます。

スキャフォールディングされたコードは上書きされるため、直接変更するのではなく、部分クラスとメソッドおよび構成をオーバーライドできる EF Core のメカニズムに依存するのが最善です。 具体的な内容は次のとおりです。

  • DbContext クラスとエンティティ クラスの両方が部分として生成されます。 これにより、スキャフォールディングの実行時にオーバーライドされない別のファイルに、追加のメンバーとコードを導入できます。
  • DbContext クラスには、OnModelCreatingPartial という部分メソッドが含まれます。 このメソッドの実装は、DbContext の部分クラスに追加できます。 その場合、それは OnModelCreating が呼び出された後で呼び出されます。
  • ModelBuilder API を使って行われたモデルの構成は、規則またはマッピング属性によって行われたすべての構成と、モデル ビルダーで行われた以前の構成をオーバーライドします。 つまり、OnModelCreatingPartial のコードを使って、スキャフォールディング プロセスによって生成された構成をオーバーライドでき、その構成を削除する必要はありません。

最後に、EF7 以降では、コードの生成に使われる T4 テンプレートをカスタマイズできることに注意してください。 これは、多くの場合、既定のものを使ってスキャフォールディングした後、部分クラスやメソッドを使って変更するより効率的なアプローチです。

しくみ

リバース エンジニアリングは、データベース スキーマを読み取ることによって開始されます。 テーブル、列、制約、およびインデックスに関する情報が読み取られます。

次に、スキーマ情報を使用して EF Core モデルを作成します。 テーブルを使用してエンティティ型を作成し、列を使用してプロパティを作成し、外部キーを使用してリレーションシップを作成します。

最後に、モデルを使用してコードを生成します。 対応するエンティティ型クラス、Fluent API、およびデータ注釈は、アプリから同じモデルを再作成するためにスキャフォールディングされます。

制限事項

  • モデルに関するすべての情報を、データベース スキーマを使用して表すことはできません。 たとえば、継承階層所有型、およびテーブル分割に関する情報は、データベース スキーマには存在しません。 このため、これらのコンストラクトはスキャフォールディングされません。
  • また、一部の列の型 は EF Core プロバイダーでサポートされない場合があります。 これらの列はモデルに含まれません。
  • EF Core モデルで同時実行トークンを定義して、2 人のユーザーが同時に同じエンティティを更新できないようにすることができます。 一部のデータベースには、この型の列を表す特殊な型 (SQL Server での rowversion など) があり、この場合はこの情報をリバース エンジニアリングできます。ただし、他の同時実行トークンはスキャフォールディングされません。