modelbuilder - Primero, el código de Entity Framework: ¿cómo anotar una clave foránea para un valor "predeterminado"?
modelbuilder entity framework (4)
El problema es que cuando tienes múltiples relaciones entre dos entidades, EF Code First no puede descubrir qué propiedades de navegación coinciden, a menos que le digas cómo, aquí está el código:
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; }
}
Con su versión anterior, EF estaba creando esa relación adicional porque no sabía que la propiedad DefaultSurvey
estaba haciendo referencia al ID
de la clase Survey
, pero puede dejarlo saber, agregando el atributo InverseProperty
cuyo parámetro es el nombre de la propiedad en la Survey
necesita DefaultSurvey
para que coincida con.
Tengo 2 clases: Cliente y Encuesta.
Cada cliente puede tener muchas encuestas, pero solo una encuesta predeterminada.
Definí las clases de esta manera:
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; }
}
Esto crea la tabla de Cliente como espero:
[dbo].[Clients]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[ClientName] [nvarchar](max) NULL,
[DefaultSurveyID] [int] NULL
)
Pero la tabla de Encuesta tiene una clave externa adicional:
[dbo].[Surveys]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[SurveyName] [nvarchar](max) NULL,
[ClientID] [int] NOT NULL,
[Client_ID] [int] NULL
)
¿Por qué Code First genera esta relación y cómo le digo que no?
Entity Framework hace exactamente lo que se le dice que haga. Lo que usted ha dicho es que existe una relación uno a muchos y uno a uno entre Clientes y Encuestas. Se generaron ambos FK en la tabla de Encuesta para mapear las dos relaciones que ha solicitado. No tiene idea de que tratas de conectar las dos relaciones, ni creo que tenga la capacidad de lidiar con eso.
Como alternativa, puede considerar agregar un campo IsDefaultSurvey
en el objeto Survey para que pueda consultar la encuesta predeterminada a través de la colección Surveys
que tiene en el objeto Client. Incluso podría ir un paso más allá y colocarlo como una propiedad NotMapped
en el objeto Client para que pueda seguir utilizando Client.DefaultSurvey
para obtener la encuesta correcta, y no tener que cambiar ninguno de sus otros códigos, de la siguiente manera:
[NotMapped]
public Survey DefaultSurvey
{
get { return this.Surveys.First(s => s.IsDefaultSurvey); }
}
Puedes hacerlo usando código primero, pero no siendo un primer experto en códigos que hice trampa :-)
1) Creé las tablas y las relaciones (como las anteriores sin el Client_ID adicional) en la base de datos mediante SMS
2) Utilicé el código de ingeniero inverso primero para crear las clases y asignaciones requeridas
3) Dejé caer la base de datos y la recreé usando context.Database.Create ()
Tabla original 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
)
)
Además de llaves extranjeras
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])
Código generado por ingeniería inversa:
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
}
Tenga en cuenta que al agregar el código a continuación, solucionará el problema.
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);
{
}