web-dev-qa-db-ja.com

EF Code-First 1対1の関係:関係のロール*で多重度は無効です

私は次のことをしようとしています:

public class class1
{
    public int Id {get;set;}
    [ForeignKey("Class2")]
    public int Class2Id {get;set;}
    public virtual Class2 Class2 {get;set;}
}

public class class2
{
    public int Id { get; set;}
    [Required]
    public virtual int Class1Id {get;set;}
    [Required]
    [ForeignKey("Class1Id")]
    public Class1 Class1 {get;set;}
}

ただし、データベースを移行しようとするたびに、次のエラーが表示されます。

Class1_Class2_Target::多重度は、関係 'Class2_Class1'のロール 'Class2_Class1_Target'では無効です。依存ロールプロパティはキープロパティではないため、依存ロールの多重度の上限は「*」である必要があります。

ここで何が問題になりますか?

28
JensOlsen112

モデルは1:1の関連付けではありません。同じone _Class2_オブジェクトを参照するmany _Class1_オブジェクトを持つことができます。また、モデルは_Class2_を参照する_Class1_がこの_Class1_オブジェクトによって参照されることも保証しません— _Class1_は_Class2_を参照できますオブジェクト。

1:1を構成する方法は?

SQLで1:1の関連付けを保証する一般的な方法は、principalエンティティのテーブルと、dependentエンティティのテーブルを用意することです。従属テーブルもプリンシパルの外部キーです。

1:1

(ここでは_Class1_がプリンシパルです)

現在、リレーショナルデータベースでは、これはまだ1:1の関連付けを保証していません(だから「並べ替え」と言いました)。 1:0..1アソシエーションです。 _Class1_なしで_Class2_を使用できます。真実は、SQLでは2つの行を異なるテーブルに同期的に挿入する言語構造がないため、SQLでは真の1:1の関連付けは不可能です。 1:0..1が最も近い値です。

フルーエントマッピング

EFでこの関連付けをモデル化するには、流れるようなAPIを使用できます。標準的な方法は次のとおりです。

_class Class1Map : EntityTypeConfiguration<Class1>
{
    public Class1Map()
    {
        this.HasKey(c => c.Id);
        this.Property(c => c.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        this.HasRequired(c1 => c1.Class2).WithRequiredPrincipal(c2 => c2.Class1);
    }
}
_

コンテキストで:

_protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new Class1Map());
}
_

そして、これはあなたのクラスの残りです:

_public class Class1
{
    public int Id {get;set;}
    public virtual Class2 Class2 {get;set;}
}

public class Class2
{
    public int Id {get;set;}
    public virtual Class1 Class1 {get;set;}
}
_

モデルに代替外部キープロパティを設定する方法はありません。これは、FKのみが関係しているためですある必要があります依存関係の主キー。

このモデルの奇妙な点は、EFが_class1_オブジェクトの作成(および保存)を止めないことですwithout a _class2_。 EFは、変更を保存する前にこの要件を検証できるはずですが、明らかにそうではありません。同様に、_class2_親を削除せずに_class1_オブジェクトを削除する方法があります。したがって、このHasRequired-WithRequiredのペアは、見た目ほど厳密ではありません(そうあるべきです)。

データ注釈

これを正しく行う唯一の方法コード内は、データ注釈によるものです。 (もちろん、データベースモデルはまだ1:1を適用できません)

_public class Class1
{
    public int Id {get;set;}
    [Required]
    public virtual Class2 Class2 {get;set;}
}

public class Class2
{
    [Key, ForeignKey("Class1")]
    public int Id {get;set;}
    [Required]
    public virtual Class1 Class1 {get;set;}
}
_

[Key, ForeignKey("Class1")]アノテーションは、_Class1_が主要なエンティティであることをEFに伝えます。

データ注釈は多くのAPIで役割を果たしますが、これは呪いになる可能性があります。各APIが実装する独自のサブセットを選択するためですが、ここではEFがデータをdesignに使用するだけでなく、モデルだけでなく、検証エンティティにも。 _class1_なしで_class2_オブジェクトを保存しようとすると、検証エラーが発生します。

58
Gert Arnold

私はまったく同じ問題を抱えていました。私が望んでいたのは、[外部キー]-> [主キー]で相互参照する2つのテーブルを持つDBスキーマです。最後に、私は方法を見つけました:2つのクラスがあるとしましょう:本と著者。 Bookクラスには、それを作成した著者への外部キーが必要です。Authorクラスには、最後に書いた本への外部キーが必要です。 EFに最初にコードを使用してこれを理解させる方法は次のとおりです(これは、データアノテーションと流れるようなAPIを組み合わせて使用​​することに注意してください)。

public class Book {
    ...
    public Guid BookId
    ...
    public Guid AuthorId { get; set; }

    [ForeignKey("AuthorId")]
    public virtual Author author { get; set; }
}

public class Author {
    ...
    public Guid AuthorId
    ...
    public Guid? LatestBookId { get; set; }

    [ForeignKey("LatestBookId")]
    public virtual Book book { get; set; }

    public virtual ICollection<Book> books { get; set; }
}

// using fluent API
class BookConfiguration : EntityTypeConfiguration<Book> {

    public BookConfiguration() {
        this.HasRequired(b => b.author)
            .WithMany(a => a.books);
    }

}

これは機能し、必要な正確なDBスキーマを作成します。 SQLでは、次のコードに対応するテーブルと外部キーを作成します。

CREATE TABLE [dbo].[Book](
    [BookId] [uniqueidentifier] NOT NULL,
    [AuthorId] [uniqueidentifier] NOT NULL,
    ...
 CONSTRAINT [PK_dbo.Book] PRIMARY KEY CLUSTERED 
(
    [BookId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

...

GO

ALTER TABLE [dbo].[Book] WITH CHECK ADD  CONSTRAINT [FK_dbo.Book.Author_AuthorId] FOREIGN KEY([AuthorId])
REFERENCES [dbo].[Author] ([AuthorId])
GO

...

CREATE TABLE [dbo].[Author](
    [AuthorId] [uniqueidentifier] NOT NULL,
    [LatestBookId] [uniqueidentifier] NULL,
    ...
 CONSTRAINT [PK_dbo.Author] PRIMARY KEY CLUSTERED 
(
    [AuthorId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

...

GO

ALTER TABLE [dbo].[Author]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Author_dbo.Book_LatestBookId] FOREIGN KEY([LatestBookId])
REFERENCES [dbo].[Book] ([BookId])
GO

...
3
Erez Lerner

2つのクラスの一方を他方の前に作成する必要があるため、[Required]アノテーションが必要です。 Class2がClass1に依存している場合、[Required、ForeignKey( "Class1")]を指定します。また、流れるようなAPIを使用して、コンテキストクラスでこれを構成することもできます。

0
RizJa