【问题标题】:Entity Framework Code First : how to annotate a foreign key for a "Default" value?实体框架代码优先:如何为“默认”值注释外键?
【发布时间】:2013-03-07 03:43:25
【问题描述】:

我有 2 个课程:客户和调查。

每个客户可以有多个调查 - 但只有一个默认调查。

我已经定义了这样的类:

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
)

但是调查表有一个额外的外键:

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

为什么 Code First 会产生这种关系,我如何告诉它不要产生这种关系?

【问题讨论】:

标签: entity-framework ef-code-first data-annotations


【解决方案1】:

问题在于,当您在两个实体之间有多个关系时,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 属性正在引用 Survey 类的 ID,但您可以让它知道,添加属性InverseProperty 的参数是Survey 中的属性名称,需要DefaultSurvey 匹配。

【讨论】:

  • 一个客户可以有许多调查,但只有一个默认调查,并且该调查应该在第一个关系定义的调查集中。
  • @Colin:对不起,我的错,这是一个错字,关系是一对多,我修正了我的答案。试试我发布的代码并回复。
  • 一项调查不能成为许多客户的默认调查,因为该调查只能链接到一个客户。因此,Survey 类不应包含 ClientsDefault 集合
  • @Colin: 抱歉,我看不到方法 :(
  • @Colin:我明白了!!!完全改变我的答案,我第一次误解你的问题,现在看看!!!
【解决方案2】:

您可以使用代码优先来做到这一点,但不是我欺骗的代码优先专家:-)

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
}

【讨论】:

  • 这是一个很好的解决方案,它使用 Fluent API 来解决问题。它真的帮助我理解了 Entity Framework +1 可以做什么
  • 只要给我链接并告诉我有关“EF 电动工具”的信息,就值得它的黄金重量。谢谢
【解决方案3】:

Entity Framework 完全按照它的指示去做。您所说的是,客户和调查之间既有一对多的关系,也有一对一的关系。它在调查表中生成了两个 FK,以便映射您请求的两个关系。它不知道你试图将这两种关系联系在一起,我认为它也没有能力处理这个问题。

作为替代方案,您可能需要考虑在调查对象上添加一个IsDefaultSurvey 字段,以便您可以通过客户端对象上的Surveys 集合查询默认调查。您甚至可以更进一步,将其作为 NotMapped 属性放在 Client 对象上,这样您仍然可以使用 Client.DefaultSurvey 来获得正确的调查,而不必更改任何其他代码,如下所示:

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

【讨论】:

  • 您必须以某种方式告诉数据库哪个是默认调查。您可以将其作为调查编辑页面的一部分进行,然后进行验证以确保客户端上只有一个默认调查。
  • 我不太明白这个。我通过将 Client 和 ClientID 属性放在 Survey 类中来指定一对多。我通过将 DefaultSurvey 和 DefaultSurveyID 放在 Client 类中指定了一对一 - 但没有什么可以说它是一对一的吗?迷茫……
  • 而 EF 正在生成 3 个关系,而不仅仅是 2 个?
  • 这是怎么回事:您通过在客户端上拥有Surveycollection 来设置一对多。您可以通过在客户端上设置 DefaultSurvey 字段来设置 1:1。 Survey 对象上的 Client 字段仅用于链接这些连接。
  • 2 种关系:集合中的一对多,单个 DefaultSurvey 字段中的 1:1。
【解决方案4】:

请注意,添加下面的代码将解决问题。

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);
    {
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-05
    • 2013-01-15
    • 1970-01-01
    • 2012-05-10
    • 2015-02-24
    • 2021-01-05
    相关资源
    最近更新 更多