1
votes

I have the following entities and context with 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);      
    });      

  }

}

Then I created a Language with translations for its names as follows:

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();

    }         

  }

}

I am creating a language with code "en" and translations for its name in English and Portuguese.

When I run the code I get the following error:

Unhandled Exception: System.InvalidOperationException: The instance of entity type 'LanguageI18N' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities.
When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.

What am I missing?

UPDATE

The code works with InMemoryDatabase and the change suggested by Ivan Stoev:

b.HasOne<Language>().WithMany().HasForeignKey(x => x.TranslationCode).IsRequired(true).OnDelete(DeleteBehavior.Restrict);

But when I replaced InMemoryDatabase:

builder.UseInMemoryDatabase();

By SQL Server database:

  builder.UseSqlServer(yourConnectionString);

I get the following error:

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.

Any idea why?

1
How are you creating your context object? Is it being injected? How have you added it to your DI container? - DavidG
I just create the Context with a connection string in a console application and it is running fine because I am able to get the count of Languages table. - Miguel Moura
So you do var context = new Context(); somewhere? (PS naming your context class Context is confusing!) - DavidG
Yes, I am ... I am using Context as type because at the moment I am just testing this. - Miguel Moura
I'm having trouble to understand your code because you are obviously cutting out bits to post here, but in doing that you have provided code that does not compile. For example language.LanguagesI18N will give you a null reference exception. There are other issues too. Can you provide a minimal reproducible example - DavidG

1 Answers

2
votes

Here

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

you are trying to map one and the same association represented by the Language -> LanguagesI18N navigation properties to two different FKs (LanguageCode and TranslationCode), which is not possible. The second line effectively overwrites the first, leading to incorrect setup.

Since you have two one-to-many relationships, and assuming the first line contains the correct setup for the first, the second can be setup by changing the last line to:

b.HasOne<Language>().WithMany().HasForeignKey(x => x.TranslationCode).IsRequired(true).OnDelete(DeleteBehavior.Restrict);

Such setup (w/o navigation property at both sides) was not possible in EF6, but perfectly supported in EF Core thanks to the parameterless overloads of the HasOne / HasMany methods. Just keep them (as well as WithOne / WithMany) in sync with your model navigation properties.