クライアントとアンケートの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がこの関係を生成するのはなぜですか?
問題は、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
が必要なことを知ってください。
あなたはコードファーストを使用してそれを行うことができますが、私が欺いたコードファーストの専門家ではありません:-)
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
}
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); }
}
以下のコードを追加すると、問題が修正されることに注意してください。
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);
{
}