2013-03-18 7 views
31

Mam 2 klasy: klienta i ankiety.Kod struktury obiektu Najpierw: jak opisywać klucz obcy pod kątem wartości "domyślnej"?

Każdy klient może mieć wiele ankiet - ale tylko jedną domyślną ankietę.

Mam zdefiniowane klasy tak:

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

Tworzy tabelę Client jak oczekuję:

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

ale tabela Survey ma dodatkowy klucz obcy:

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

Dlaczego Code First generuje tę relację i jak jej odmówić?

+0

Różne sposoby modelowania tego problemu w bazie danych: http://stackoverflow.com/q/5244920/150342 – Colin

Odpowiedz

42

Problem polega na tym, że gdy masz wiele relacji pomiędzy dwoma podmiotami, kod EF First nie jest w stanie ustalić, które właściwości nawigacji pasują do siebie, un mniej, jak to powiedzieć, oto kod:

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

Dzięki swojej poprzedniej wersji, EF było stworzenie, które dodatkowo związek, ponieważ nie wiedział, że nieruchomość została DefaultSurvey odwoływania się ID klasy Survey, ale możesz poinformować go, że dodanie atrybutu InverseProperty, którego parametr jest nazwą właściwości w Survey, wymaga dopasowania, aby pasowało do DefaultSurvey.

+0

Klient może mieć wiele ankiet, ale tylko jedną domyślną ankietę, a ta ankieta powinna znajdować się w zestawie ankiet zdefiniowanych przez pierwszą relację. – Colin

+0

@Colin: Przepraszam, moja zła, to był literówka, relacje są ** jeden-do-wielu **, poprawiłem odpowiedź. Wypróbuj kod, który opublikowałem i odpowiedz. – ecampver

+0

Ankieta nie może być domyślną ankietą dla wielu klientów, ponieważ ankieta może być połączona tylko z jednym klientem.Dlatego klasa Survey nie powinna zawierać kolekcji ClientsDefault – Colin

2

Entity Framework robi dokładnie to, co zostało powiedziane. Powiedziałeś, że istnieje zarówno relacja jeden-do-wielu, jak i jeden-do-jednego między klientami a ankietami. Wygenerował oba FK w tabeli Ankiety, aby odwzorować obie relacje, o które prosiłeś. Nie ma pojęcia, że ​​próbujesz połączyć te dwie relacje razem, ani nie sądzę, że ma zdolność radzenia sobie z tym.

Jako alternatywę warto rozważyć dodanie pola IsDefaultSurvey w obiekcie Ankieta, aby można było wykonać zapytanie o domyślną ankietę za pośrednictwem kolekcji Surveys, która znajduje się w obiekcie klienta. Możesz nawet posunąć się o krok dalej i umieścić go jako właściwość NotMapped w obiekcie klienta, dzięki czemu możesz nadal używać Client.DefaultSurvey, aby uzyskać prawidłową ankietę i nie musisz zmieniać żadnego innego kodu, jak poniżej:

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

To miłe sztuczka, ale ustawienie to nie jest tak łatwo jest? – Colin

+0

Musisz jakoś powiedzieć bazie danych, która z nich jest domyślną ankietą. Możesz to zrobić jako część strony edycji ankiety, a następnie przeprowadzić walidację, aby upewnić się, że istnieje tylko jedna domyślna ankieta na kliencie. – IronMan84

+0

Nie do końca rozumiem. Podałem wartość jeden-do-wielu, umieszczając właściwości Client i ClientID w klasie Ankieta. Określam jeden do jednego, umieszczając DefaultSurvey i DefaultSurveyID w klasie Client - ale nie ma nic do powiedzenia, że ​​jest tam jeden-do-jednego? Confused .... – Colin

11

można to zrobić za pomocą kodu pierwszy, ale nie będąc kod najpierw eksperta oszukiwałem :-)

1) i utworzone tabele i relacje (jak wyżej bez dodatkowego CLIENT_ID) w bazie danych za pomocą SMS

2) Użyłem Reverse Engineer Code First do utworzenia wymaganych klas i odwzorowań

3) Porzuciłem bazę danych i ponownie ją utworzyłem przy pomocy context.Database.Create()

oryginalne defs stołu:

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 klucze obce

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

kod generowany przez 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 
} 
+0

Myślę, że byłoby bardzo przydatnym dodanie kodu, jeśli możesz. – Colin

+0

To doskonałe rozwiązanie, które wykorzystuje Fluent API do rozwiązania problemu. To naprawdę pomogło mi zrozumieć, co można zrobić z Entity Framework +1 – Colin

+1

Wystarczy podać link i powiedzieć mi, że "Elektronarzędzia EF" były warte swojej wagi w złocie. Dzięki –

1

Należy pamiętać, że dodanie poniższego kodu rozwiązuje problem.

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