web-dev-qa-db-ja.com

Entity Framework Code First:「デフォルト」値の外部キーに注釈を付ける方法は?

クライアントとアンケートの2つのクラスがあります。

各クライアントは多くの調査を行うことができますが、デフォルトの調査は1つだけです。

私はこのようにクラスを定義しました:

public class Client
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    public string ClientName { get; set; }

    public Nullable<int> DefaultSurveyID { get; set; }

    [ForeignKey("DefaultSurveyID")]
    public virtual Survey DefaultSurvey { get; set; }

    public virtual ICollection<Survey> Surveys { get; set; }
}

public class Survey
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    public string SurveyName { get; set; }

    [Required]
    public int ClientID { get; set; }

    [ForeignKey("ClientID")]
    public virtual Client Client { get; set; }
}

これにより、期待どおりにClientテーブルが作成されます。

[dbo].[Clients]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[ClientName] [nvarchar](max) NULL,
[DefaultSurveyID] [int] NULL
)

ただし、Surveyテーブルには余分な外部キーがあります。

[dbo].[Surveys]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[SurveyName] [nvarchar](max) NULL,
[ClientID] [int] NOT NULL,
[Client_ID] [int] NULL
)

Code Firstがこの関係を生成するのはなぜですか?

31
Colin

問題は、2つのエンティティ間に複数の関係がある場合、EF Code Firstはどのナビゲーションプロパティが一致するかを見つけることができないことです。

public class Client
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    public string ClientName { get; set; }

    /****Change Nullable<int> by int?, looks better****/
    public int? DefaultSurveyID { get; set; }

    /****You need to add this attribute****/
    [InverseProperty("ID")]
    [ForeignKey("DefaultSurveyID")]
    public virtual Survey DefaultSurvey { get; set; }

    public virtual ICollection<Survey> Surveys { get; set; }
}

前のバージョンでは、EFはDefaultSurveyプロパティがIDクラスのSurveyを参照していることを知らなかったため、その余分な関係を作成していましたが、それを許可することができますパラメータがInversePropertyのプロパティの名前である属性Surveyを追加すると、一致するDefaultSurveyが必要なことを知ってください。

42
ecampver

あなたはコードファーストを使用してそれを行うことができますが、私が欺いたコードファーストの専門家ではありません:-)

1)SMSを使用して、データベースにテーブルとリレーションシップ(追加のClient_IDなしで)を作成しました

2) Reverse Engineer Code First を使用して、必要なクラスとマッピングを作成しました

3)データベースを削除し、context.Database.Create()を使用して再作成しました

元のテーブル定義:

CREATE TABLE [dbo].[Client](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NULL,
    [DefaultSurveyId] [int] NULL,
     CONSTRAINT [PK_dbo.Client] PRIMARY KEY NONCLUSTERED 
    (
        [Id] ASC
    )
)

CREATE TABLE [dbo].[Survey](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NULL,
    [ClientId] [int] NULL,
     CONSTRAINT [PK_dbo.Survey] PRIMARY KEY NONCLUSTERED 
    (
        [Id] ASC
    )
)

プラス外部キー

ALTER TABLE [dbo].[Survey]  WITH CHECK 
    ADD CONSTRAINT [FK_dbo.Survey_dbo.Client_ClientId] FOREIGN KEY([ClientId])
    REFERENCES [dbo].[Client] ([Id])

ALTER TABLE [dbo].[Client]  WITH CHECK 
    ADD CONSTRAINT [FK_dbo.Client_dbo.Survey_DefaultSurveyId] 
    FOREIGN KEY([DefaultSurveyId]) REFERENCES [dbo].[Survey] ([Id])

リバースエンジニアリングによって生成されたコード:

public partial class Client
{
    public Client()
    {
        this.Surveys = new List<Survey>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public int? DefaultSurveyId { get; set; }
    public virtual Survey DefaultSurvey { get; set; }
    public virtual ICollection<Survey> Surveys { get; set; }
}

public partial class Survey
{
    public Survey()
    {
        this.Clients = new List<Client>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public int? ClientId { get; set; }
    public virtual ICollection<Client> Clients { get; set; }
    public virtual Client Client { get; set; }
}

public class ClientMap : EntityTypeConfiguration<Client>
{
    #region Constructors and Destructors

    public ClientMap()
    {
        // Primary Key
        this.HasKey(t => t.Id);

        // Properties
        this.Property(t => t.Name).HasMaxLength(50);

        // Table & Column Mappings
        this.ToTable("Client");
        this.Property(t => t.Id).HasColumnName("Id");
        this.Property(t => t.Name).HasColumnName("Name");
        this.Property(t => t.DefaultSurveyId).HasColumnName("DefaultSurveyId");

        // Relationships
        this.HasOptional(t => t.DefaultSurvey)
            .WithMany(t => t.Clients).HasForeignKey(d => d.DefaultSurveyId);
    }

    #endregion
}

public class SurveyMap : EntityTypeConfiguration<Survey>
{
    #region Constructors and Destructors

    public SurveyMap()
    {
        // Primary Key
        this.HasKey(t => t.Id);

        // Properties
        this.Property(t => t.Name).HasMaxLength(50);

        // Table & Column Mappings
        this.ToTable("Survey");
        this.Property(t => t.Id).HasColumnName("Id");
        this.Property(t => t.Name).HasColumnName("Name");
        this.Property(t => t.ClientId).HasColumnName("ClientId");

        // Relationships
        this.HasOptional(t => t.Client)
            .WithMany(t => t.Surveys).HasForeignKey(d => d.ClientId);
    }

    #endregion
}
11
Phil

Entity Frameworkは、指示どおりに実行します。あなたが言ったことは、クライアントと調査の間には一対多と一対一の関係があるということです。要求した両方の関係をマッピングするために、Surveyテーブルに両方のFKを生成しました。あなたが2つの関係を結びつけようとしているという考えもありませんし、それに対処する能力があるとは思いません。

別の方法として、SurveyオブジェクトにIsDefaultSurveyフィールドを追加して、ClientオブジェクトにあるSurveysコレクションを介してデフォルトの調査を照会できるようにすることもできます。さらに一歩進んで、ClientオブジェクトのNotMappedプロパティとして追加して、Client.DefaultSurvey次のように正しい調査を取得し、他のコードを変更する必要はありません。

[NotMapped]
public Survey DefaultSurvey
{
  get { return this.Surveys.First(s => s.IsDefaultSurvey); }
}
2
IronMan84

以下のコードを追加すると、問題が修正されることに注意してください。

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext() : base("DefaultConnection")
    {

    }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
          modelBuilder.Entity<Client>()
                      .HasOptional(x => x.DefaultSurvey)
                      .WithMany(x => x.Surveys);
                      .HasForeignKey(p => p.DefaultSurveyID);
    {
}
1