EFコードナビゲーションプロパティのない最初の外部キー


91

次のエンティティがあるとしましょう。

public class Parent
{
    public int Id { get; set; }
}
public class Child
{
    public int Id { get; set; }
    public int ParentId { get; set; }
}

ナビゲーションプロパティを必要とせずに、 ParentIdがParentsテーブルへの外部キー制約を使用してデータベースに作成されるようにするためのコードファーストの流暢なAPI構文は何ですか?

ナビゲーションプロパティの親を子に追加すると、次のことができることを知っています。

modelBuilder.Entity<Child>()
    .HasRequired<Parent>(c => c.Parent)
    .WithMany()
    .HasForeignKey(c => c.ParentId);

しかし、この特定のケースでは、ナビゲーションプロパティは必要ありません。


1
これはEFだけで実際に可能だとは思いません。おそらく、手動移行で生のSQLを使用してセットアップする必要があります
は愛されていません

@LukeMcGregorそれは私が恐れていたものです。あなたがその答えをするならば、それが正しいと仮定して、私はそれを喜んで受け入れます。:-)
RationalGeek

ナビゲーションプロパティがない特別な理由はありますか?ナビゲーションプロパティをプライベートに機能させますか?エンティティの外部には表示されませんが、EFを喜ばせます。(注私はこれを試していないが、私は、これは動作するはずだと思う-プライベートプロパティのマッピングについては、この記事を見てみromiller.com/2012/10/01/...
パヴェル

6
まあ、私はそれを必要としないので、私はそれを望んでいません。フレームワークの要件を満たすためだけに、不要なものをデザインに追加する必要はありません。ナビゲーションプロップを入れるのは私を殺しますか?いいえ。実際、それは私が当分の間行ったことです。
RationalGeek

リレーションを構築するには、少なくとも片側にナビゲーションプロパティが常に必要です。より多くの情報についてはstackoverflow.com/a/7105288/105445
ワヒドビタール

回答:


63

EF Code First Fluent APIでは、それは不可能です。データベースに外部キー制約を作成するには、常に少なくとも1つのナビゲーションプロパティが必要です。

コードファーストマイグレーションを使用している場合は、パッケージマネージャーコンソール(add-migration SomeNewSchemaName)で新しいコードベースのマイグレーションを追加するオプションがあります。モデルまたはマッピングで何かを変更した場合、新しい移行が追加されます。何も変更しなかった場合は、を使用して新しい移行を強制しadd-migration -IgnoreChanges SomeNewSchemaNameます。この場合Up、移行には空のDownメソッドとメソッドのみが含まれます。

次にUp、以下を追加してメソッドを変更できます。

public override void Up()
{
    // other stuff...

    AddForeignKey("ChildTableName", "ParentId", "ParentTableName", "Id",
        cascadeDelete: true); // or false
    CreateIndex("ChildTableName", "ParentId"); // if you want an index
}

この移行を(update-databaseパッケージ管理コンソールで)実行すると、次のようなSQLステートメントが実行されます(SQL Serverの場合)。

ALTER TABLE [ChildTableName] ADD CONSTRAINT [FK_SomeName]
FOREIGN KEY ([ParentId]) REFERENCES [ParentTableName] ([Id])

CREATE INDEX [IX_SomeName] ON [ChildTableName] ([ParentId])

または、移行せずに、を使用して純粋なSQLコマンドを実行することもできます。

context.Database.ExecuteSqlCommand(sql);

ここで、contextは派生コンテキストクラスのインスタンスであり、sql文字列としての上記のSQLコマンドです。

このすべてで、EFにはParentId関係を説明する外部キーである手がかりがないことに注意してください。EFはそれを通常のスカラープロパティとしてのみ見なします。どういうわけか、上記のすべては、SQL管理ツールを開いて手動で制約を追加する場合に比べて、より複雑で時間がかかる方法にすぎません。


2
単純化は自動化によるものです。コードがデプロイされている他の環境にアクセスできません。コードでこれらの変更を実行できることは私にとって素晴らしいことです。しかし、私はスナークが好きです:)
pomeroy 2016

エンティティに属性を設定しただけなので、親IDにを追加するだけだと思います[ForeignKey("ParentTableName")]。これにより、プロパティが親テーブルにあるキーにリンクされます。これで、ハードコードされたテーブル名ができました。
Triynko 2018

2
これは明らかに不可能ではないですが、なぜ、以下の他のコメントがこれも正しい答えとしてマークされている参照
イゴールはして

111

この投稿は対象外ですEntity FrameworkEntity Framework Core、Entity Framework Coreを使用して同じことを達成したい人には役立つかもしれません(私はV1.1.2を使用しています)。

私はDDDを練習ParentChildていて、2つの別々の集約ルートになりたいので、ナビゲーションプロパティは必要ありません(それらは素晴らしいですが)。インフラストラクチャ固有のEntity Frameworkナビゲーションプロパティではなく、外部キーを介して相互に通信できるようにしたいと考えています。

あなたがしなければならないのは、ナビゲーションプロパティを使用してHasOne、またはWithMany指定せずに一方の関係を構成することです(結局、それらはそこにありません)。

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}

    protected override void OnModelCreating(ModelBuilder builder)
    {
        ......

        builder.Entity<Parent>(b => {
            b.HasKey(p => p.Id);
            b.ToTable("Parent");
        });

        builder.Entity<Child>(b => {
            b.HasKey(c => c.Id);
            b.Property(c => c.ParentId).IsRequired();

            // Without referencing navigation properties (they're not there anyway)
            b.HasOne<Parent>()    // <---
                .WithMany()       // <---
                .HasForeignKey(c => c.ParentId);

            // Just for comparison, with navigation properties defined,
            // (let's say you call it Parent in the Child class and Children
            // collection in Parent class), you might have to configure them 
            // like:
            // b.HasOne(c => c.Parent)
            //     .WithMany(p => p.Children)
            //     .HasForeignKey(c => c.ParentId);

            b.ToTable("Child");
        });

        ......
    }
}

私はよくとして構成する方法エンティティプロパティに例を配っていますが、ここで最も重要なものであるHasOne<>WithMany()HasForeignKey()

それが役に立てば幸い。


8
これはEFCoreの正解であり、DDDを実践している人にとってはこれは必須です。
チアゴ・シウバ

1
ナビゲーションプロパティを削除するために何が変更されたかはわかりません。明確にしていただけますか?
andrew.rockwell

3
@ andrew.rockwell:HasOne<Parent>()およびの.WithMany()子構成を参照してください。とにかく定義されたナビゲーションプロパティがないため、これらはナビゲーションプロパティをまったく参照しません。私は私のアップデートでそれをより明確にするように努めます。
デヴィッド・梁

驚くばかり。ありがとう@ DavidLiang
andrew.rockwell19年

2
OPの特定のコードサンプルとは関係のない@ mirind4、DDDによると、異なる集約ルートエンティティをそれぞれのDBテーブルにマッピングする場合、それらのルートエンティティは、IDを介してのみ相互に参照する必要があり、完全な参照を含めることはできません。他のARエンティティに。実際、DDDでは、int / log / guid(特にARエンティティ)などのプリミティブ型の小道具を使用する代わりに、エンティティのIDを値オブジェクトにすることも一般的です。これにより、プリミティブな執着を回避し、さまざまなARが値を介してエンティティを参照できるようになります。オブジェクトIDタイプ。HTH
チアゴシルバ

21

DataAnotationsを使用したいが、ナビゲーションプロパティを公開したくない人のための小さなヒント-使用 protected

public class Parent
{
    public int Id { get; set; }
}
public class Child
{
    public int Id { get; set; }
    public int ParentId { get; set; }

    protected virtual Parent Parent { get; set; }
}

厥それは-と、外部キーcascade:trueの後にはAdd-Migration作成されます。


1
プライベートでも可能
Marc Wittke

2
子を作成するとき、割り当てるParent必要がありParentIdますか、それとも単にですか?
ジョージマウアー2015年

5
@MarcWittkevirtualプロパティはできませんprivate
LINQ 2016年

@GeorgeMauer:それは良い質問です!技術的にはどちらか一方が動作しますが、あなたはこのような一貫性のないコードがある場合、開発者(特に新規参入が)に渡すことがわからないので、それはまた、問題になります。
デビッド・リャン

14

EF Coreの場合、必ずしもナビゲーションプロパティを提供する必要はありません。関係の片側に外部キーを指定するだけです。Fluent APIを使用した簡単な例:

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;

namespace EFModeling.Configuring.FluentAPI.Samples.Relationships.NoNavigation
{
    class MyContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
             modelBuilder.Entity<Post>()
                .HasOne<Blog>()
                .WithMany()
                .HasForeignKey(p => p.BlogId);
        }
    }

    public class Blog
    {
         public int BlogId { get; set; }
         public string Url { get; set; }
    }

    public class Post
    {
         public int PostId { get; set; }
         public string Title { get; set; }
         public string Content { get; set; }

        public int BlogId { get; set; }
    }
}

2

.Net Core 3.1、EntityFramework3.1.3を使用しています。私は探し回っていましたが、私が思いついた解決策は、の汎用バージョンを使用することでしたHasForeginKey<DependantEntityType>(e => e.ForeginKeyProperty)。次のように1対1の関係を作成できます。

builder.entity<Parent>()
.HasOne<Child>()
.WithOne<>()
.HasForeginKey<Child>(c => c.ParentId);

builder.entity<Child>()
    .Property(c => c.ParentId).IsRequired();

これがHasForeginKeyメソッドの使用方法に関する他のアイデアに役立つか、少なくとも提供されることを願っています。


0

ナビゲーションプロパティを使用しない理由は、クラスの依存関係です。モデルをいくつかのアセンブリに分割しました。これらのアセンブリは、さまざまなプロジェクトで任意の組み合わせで使用できる場合と使用できない場合があります。したがって、別のアセンブリからクラス化するためのnagivationプロパティを持つエンティティがある場合は、そのアセンブリを参照する必要があります。これは避けたいものです(または、その完全なデータモデルの一部を使用するプロジェクトではすべてが実行されます)。

また、移行(自動移行を使用)と最初のDB作成に使用される個別の移行アプリがあります。このプロジェクトは、明らかな理由ですべてを参照しています。

解決策はCスタイルです:

  • リンクを介してターゲットクラスのファイルを移行プロジェクトに「コピー」します(altVSのキーを使用してドラッグアンドドロップ)
  • nagivationプロパティ(およびFK属性)を無効にする #if _MIGRATION
  • 移行アプリでそのプリプロセッサ定義を設定し、モデルプロジェクトでは設定しないため、何も参照しません(Contact例ではクラスを使用してアセンブリを参照しないでください)。

サンプル:

    public int? ContactId { get; set; }

#if _MIGRATION
    [ForeignKey(nameof(ContactId))]
    public Contact Contact { get; set; }
#endif

もちろん、同じようにusingディレクティブを無効にして名前空間を変更する必要があります。

その後、すべてのコンシューマーはそのプロパティを通常のDBフィールドとして使用できます(必要がない場合は追加のアセンブリを参照しません)が、DBサーバーはそれがFKであることを認識し、カスケードを使用できます。非常に汚れた解決策。しかし、動作します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.