web-dev-qa-db-fra.com

Entity Framework Code First: comment annoter une clé étrangère pour une valeur "Default"?

J'ai 2 classes: Client et Sondage.

Chaque client peut avoir plusieurs enquêtes - mais une seule enquête par défaut.

J'ai défini les classes comme ceci:

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

Cela crée la table Client comme je l'attends:

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

Mais la table Survey a une clé étrangère supplémentaire:

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

Pourquoi Code First génère-t-il cette relation et comment le lui dire?

31
Colin

Le problème est que lorsque vous avez plusieurs relations entre deux entités, EF Code First n'est pas en mesure de savoir quelles propriétés de navigation correspondent, sauf si vous lui dites comment, voici le code:

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

Avec votre version précédente, EF créait cette relation supplémentaire car il ne savait pas que la propriété DefaultSurvey faisait référence à la ID de la classe Survey, mais vous pouvez la laisser sachez que, en ajoutant l'attribut InverseProperty dont le paramètre est le nom de la propriété dans Survey, vous avez besoin de DefaultSurvey pour correspondre.

42
ecampver

Vous pouvez le faire en utilisant le code en premier, mais n'étant pas un premier expert en code, j'ai triché :-)

1) J'ai créé les tables et les relations (comme ci-dessus sans le Client_ID supplémentaire) dans la base de données en utilisant SMS

2) J'ai utilisé Reverse Engineering Code First pour créer les classes et les mappages requis

3) J'ai supprimé la base de données et l'ai recréée à l'aide de context.Database.Create ()

Défauts de table d'origine:

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
    )
)

Plus les clés étrangères

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])

Code généré par reverse engineering:

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 fait exactement ce qu'on lui dit de faire. Ce que vous lui avez dit, c'est qu'il existe à la fois une relation un à plusieurs et une à une entre les clients et les enquêtes. Il a généré les deux FK dans la table Survey afin de mapper les deux relations que vous avez demandées. Il n'a aucune idée que vous essayez de relier les deux relations, et je ne pense pas non plus qu'il ait la capacité de gérer cela.

Vous pouvez également envisager d'ajouter un champ IsDefaultSurvey sur l'objet Survey afin de pouvoir interroger l'enquête par défaut via la collection Surveys que vous avez sur l'objet Client. Vous pouvez même aller plus loin et la mettre en tant que propriété NotMapped sur l'objet Client afin de pouvoir utiliser Client.DefaultSurvey pour obtenir le bon sondage, et ne pas avoir à modifier l'un de vos autres codes, comme suit:

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

Veuillez noter que l'ajout du code ci-dessous résoudra le problème.

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
Rafael Fernandes