Ho due entità User
e Profile
. Ciascuna di queste entità eredita da una classe Entity
che contiene una proprietà Id
. La relazione tra l'utente e il profilo è una relazione uno a uno. Sto tentando di utilizzare lo stesso ID creato per l'entità Utente come ID per l'entità profilo, ovvero User.Id == Profile.Id
.
Ho cercato di farlo con FluentAPI ma non ci riesco. Ho letto questa domanda e ho letto anche questo post , ma non riesco ancora a trovare una soluzione al mio problema.
Ecco il codice per le mie entità:
public class Entity<TKey> : IEntity
{
/// <summary>
/// Unique identifier for this entity.
/// </summary>
public virtual int Id { get; set; }
}
public class User : Entity<int>
{
public virtual Profile ProfileItem { get; set; }
}
public class Profile : Entity<int>
{
}
Ecco le mie prove fino ad ora, in cui tutti hanno fallito:
Questa mappatura ha generato un oggetto ProfileItemId
nella tabella User
:
modelBuilder.Entity<User>().HasOne(u => u.ProfileItem)
.WithOne()
.HasForeignKey<Profile>(p => p.Id)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
Ho provato ad aggiungere una proprietà di navigazione inversa nell'entità Profile
per User
. La tabella User
stata mappata correttamente, ma una colonna UserId
stata aggiunta alla tabella Profile
:
modeBuilder.Entity<User>().HasOne(u => u.ProfileItem)
.WithOne(u => u.User)
.HasForeignKey<Profile>(p => p.Id)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
L'unica soluzione per questo problema, come riportato nella domanda che ho menzionato in precedenza, è che devi aggiungere una proprietà inversa e quindi utilizzare Annotazioni dei dati. Ho provato quanto segue e ha funzionato:
public class Entity<TKey> : IEntity
{
/// <summary>
/// Unique identifier for this entity.
/// </summary>
public virtual int Id { get; set; }
}
public class User : Entity<int>
{
[InverseProperty("User")]
public virtual Profile ProfileItem { get; set; }
}
public class Profile : Entity<int>
{
[ForeignKey("Id")]
public virtual User User { get; set; }
}
Come far funzionare lo stesso con FluentAPI?
Quello che @Ivan ha detto è corretto. Nell'esempio che ho postato, ho eseguito i mapping sull'evento OnModelCreating, ma nel mio codice effettivo, ho usato una classe base per la mappatura e questo era il problema.
Ecco il mio codice corrente che ha prodotto il problema:
Una classe base per la mappatura delle entità
public class EntityTypeConfigurationBase<T> : IEntityTypeConfiguration<T> where T : class, IEntity
{
private bool BIgnoreBaseProps = true;
public EntityTypeConfigurationBase(bool bIgnoreBaseProps = true) : base()
{
BIgnoreBaseProps = bIgnoreBaseProps;
}
public void Configure(EntityTypeBuilder<T> builder)
{
if (BIgnoreBaseProps)
{
builder.Ignore(x => x.State);
builder.Ignore(x => x.AssociationState);
}
}
}
La mia classe di mappatura
public class UserMap : EntityTypeConfigurationBase<User>
{
new public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasKey(u => u.Id);
builder
.HasOne(u => u.ProfileItem)
.WithOne()
.HasForeignKey<Profile>(p => p.Id)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
builder.ToTable("User");
base.Configure(builder);
}
}
Il problema era nella classe di mappatura, nel metodo Configure. Nella classe base, avevo bisogno di ignorare alcune proprietà condivise e, nelle classi di mappatura, avevo bisogno di fare i miei mapping personalizzati. Il metodo utilizzato è denominato Configure
che deve essere implementato per la mappatura. Poiché lo stesso nome del metodo viene utilizzato per entrambi, la base e le classi derivate, ciò ha comportato una chiamata duplicata. La prima chiamata era per il metodo del tipo di base e la seconda era per le classi di mapping. La mappatura è stata eseguita due volte e la chiave esterna (il mio problema) è stata generata nella prima mappatura.
La soluzione a questo problema era contrassegnare il metodo del tipo di base come virtual
e quindi sovrascrivere il metodo sul tipo di base. La chiamata al metodo type di base prenderà lo stesso oggetto ModelBuilder
e non duplicherà nuovamente il mapping.