Код Framework Entity Сначала: как комментировать внешний ключ для значения "По умолчанию"?
У меня есть 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; }
}
Это создает таблицу клиента, как я ожидаю:
[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 генерирует это отношение и как мне сказать это не?
Ответы
Ответ 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
ссылалось на ID
класса Survey
, но вы можете сообщить ему, что добавление атрибута InverseProperty
, параметром которого является имя свойства в Survey
, вам нужно DefaultSurvey
для соответствия с.
Ответ 2
Вы можете сделать это, используя код сначала, но не являясь первым экспертом кода, которого я обманул: -)
1) Я создал таблицы и отношения (как указано выше без дополнительного Client_ID) в базе данных с помощью SMS
2) Я использовал Обратный код инженера Сначала, чтобы создать необходимые классы и сопоставления
3) Я удалил базу данных и воссоздал ее с помощью context.Database.Create()
Исходная таблица defs:
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
}
Ответ 3
Entity Framework делает именно то, что он сказал. То, что вы сказали, это то, что между Клиентами и Обзорами существуют отношения "один ко многим" и "один к одному". Он сгенерировал оба FK в таблице Survey, чтобы сопоставить оба запрошенных вами отношения. Он не подозревает, что вы пытаетесь связать эти два отношения вместе, и я не думаю, что у него есть возможность справиться с этим.
В качестве альтернативы вам может потребоваться добавить поле IsDefaultSurvey
объекта Survey, чтобы вы могли запросить опрос по умолчанию с помощью коллекции Surveys
, которую вы имеете на объекте Client. Вы даже можете сделать еще один шаг и поместить его в качестве свойства NotMapped
на объект Client, чтобы вы могли использовать Client.DefaultSurvey
для получения правильного опроса и не должны изменять какой-либо другой код следующим образом:
[NotMapped]
public Survey DefaultSurvey
{
get { return this.Surveys.First(s => s.IsDefaultSurvey); }
}
Ответ 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);
{
}