Ho le seguenti entità e il contesto con Entity Framework Core 1.1:
public class Language {
public String LanguageCode { get; set; }
public virtual ICollection<LanguageI18N> LanguagesI18N { get; set; } = new List<LanguageI18N>();
}
public class LanguageI18N {
public String LanguageCode { get; set; }
public String TranslationCode { get; set; }
public String Name { get; set; }
public virtual Language Language { get; set; }
}
public class Context : DbContext {
public DbSet<Language> Languages { get; set; }
public DbSet<LanguageI18N> LanguagesI18N { get; set; }
public Context(DbContextOptions options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder) {
base.OnModelCreating(builder);
builder.Entity<Language>(b => {
b.ToTable("Languages");
b.HasKey(x => x.LanguageCode);
b.Property(x => x.LanguageCode).IsRequired(true).HasMaxLength(2).ValueGeneratedNever();
});
builder.Entity<LanguageI18N>(b => {
b.ToTable("LanguagesI18N");
b.HasKey(x => new { x.LanguageCode, x.TranslationCode });
b.Property(x => x.LanguageCode).HasMaxLength(2).IsRequired(true);
b.Property(x => x.TranslationCode).HasMaxLength(2).IsRequired(true);
b.HasOne(x => x.Language).WithMany(x => x.LanguagesI18N).HasForeignKey(x => x.LanguageCode).IsRequired(true).OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.Language).WithMany(x => x.LanguagesI18N).HasForeignKey(x => x.TranslationCode).IsRequired(true).OnDelete(DeleteBehavior.Restrict);
});
}
}
Quindi ho creato una lingua con traduzioni per i suoi nomi come segue:
public class Program {
public static void Main(String[] args) {
DbContextOptionsBuilder builder = new DbContextOptionsBuilder<Context>();
builder.UseInMemoryDatabase();
using (Context context = new Context(builder.Options)) {
Language language = new Language { LanguageCode = "en" };
language.LanguagesI18N.Add(new LanguageI18N { LanguageCode = "en", TranslationCode = "en", Name = "English" });
language.LanguagesI18N.Add(new LanguageI18N { LanguageCode = "en", TranslationCode = "pt", Name = "Inglês" });
context.Languages.Add(language);
context.SaveChanges();
}
}
}
Sto creando una lingua con il codice "en" e le traduzioni per il suo nome in inglese e portoghese.
Quando eseguo il codice ottengo il seguente errore:
Eccezione non gestita: System.InvalidOperationException: l'istanza del tipo di entità "LanguageI18N" non può essere tracciata perché un'altra traccia di questo tipo con la stessa chiave è già stata tracciata. Quando si aggiungono nuove entità, per la maggior parte dei tipi di chiave verrà creato un valore di chiave temporanea univoco se non viene impostata alcuna chiave (cioè se alla proprietà della chiave viene assegnato il valore predefinito per il suo tipo). Se si impostano in modo esplicito valori chiave per le nuove entità, assicurarsi che non entrino in collisione con entità esistenti o valori temporanei generati per altre nuove entità.
Quando si allegano entità esistenti, assicurarsi che solo una istanza di entità con un dato valore chiave sia allegata al contesto.
Cosa mi manca?
AGGIORNARE
Il codice funziona con InMemoryDatabase e il cambiamento suggerito da Ivan Stoev:
b.HasOne<Language>().WithMany().HasForeignKey(x => x.TranslationCode).IsRequired(true).OnDelete(DeleteBehavior.Restrict);
Ma quando ho sostituito InMemoryDatabase:
builder.UseInMemoryDatabase();
Dal database SQL Server:
builder.UseSqlServer(yourConnectionString);
Ottengo il seguente errore:
Unhandled Exception: Microsoft.EntityFrameworkCore.DbUpdateException:
An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_LanguagesI18N_Languages_TranslationCode".
The conflict occurred in database "TestDb", table "dbo.Languages", column 'LanguageCode'.
The statement has been terminated.
Qualche idea del perché?
Qui
b.HasOne(x => x.Language).WithMany(x => x.LanguagesI18N).HasForeignKey(x => x.LanguageCode).IsRequired(true).OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.Language).WithMany(x => x.LanguagesI18N).HasForeignKey(x => x.TranslationCode).IsRequired(true).OnDelete(DeleteBehavior.Restrict);
si sta tentando di mappare una sola associazione rappresentata dalle proprietà di navigazione Language
-> LanguagesI18N
a due diversi FK ( LanguageCode
e TranslationCode
), che non è possibile. La seconda riga sovrascrive in modo efficace la prima, portando a una configurazione errata.
Dato che hai due relazioni one-to-many
, e supponendo che la prima riga contenga l'impostazione corretta per la prima, la seconda può essere impostata cambiando l'ultima linea in:
b.HasOne<Language>().WithMany().HasForeignKey(x => x.TranslationCode).IsRequired(true).OnDelete(DeleteBehavior.Restrict);
Tale configurazione (senza proprietà di navigazione su entrambi i lati) non era possibile in EF6, ma perfettamente supportata in EF Core grazie ai sovraccarichi senza parametri dei metodi HasOne
/ HasMany
. Basta tenerli (così come WithOne
/ WithMany
) in sincronia con le proprietà di navigazione del tuo modello.