Sono nuovo di Asp.Net Core (anche per Asp.Net e web). Sto usando Asp.Net Core 2 con MySQL, usando il driver Pomelo.EntityFrameWorkCore.MySql (2.0.1). Ho appena creato un dbcontext personalizzato con la tabella Courses and Enrollments, insieme al predefinito ApplicationDbContext. La chiave primaria per le iscrizioni è una chiave composta, comprendente UserId e CourseId. Di seguito è riportato il codice:
public class CustomDbContext : DbContext
{
public virtual DbSet<Courses> Courses { get; set; }
public virtual DbSet<Enrollments> Enrollments { get; set; }
public CustomDbContext(DbContextOptions<CustomDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Courses>(entity =>
{
entity.ToTable("courses");
entity.HasIndex(e => e.Name)
.HasName("Coursescol_UNIQUE")
.IsUnique();
entity.Property(e => e.Id).HasColumnType("int(11)");
entity.Property(e => e.Duration).HasColumnType("time");
entity.Property(e => e.Name).HasMaxLength(45);
});
modelBuilder.Entity<Enrollments>(entity =>
{
entity.HasKey(e => new { e.UserId, e.CourseId });
entity.ToTable("enrollments");
entity.HasIndex(e => e.CourseId)
.HasName("fk_Courses_Enrollments_CourseId_idx");
entity.HasIndex(e => e.UserId)
.HasName("fk_Users_Enrollments_CourseId_idx");
entity.HasIndex(e => new { e.UserId, e.CourseId })
.HasName("UniqueEnrollment")
.IsUnique();
entity.Property(e => e.CourseId).HasColumnType("int(11)");
entity.HasOne(d => d.Course)
.WithMany(p => p.Enrollments)
.HasForeignKey(d => d.CourseId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_Courses_Enrollments_CourseId");
entity.HasOne(d => d.User)
.WithMany(p => p.Enrollments)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_Users_Enrollments_UserId");
});
}
}
Il Program.cs va come:
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<CustomDbContext>();
context.Database.EnsureCreated();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
Il metodo configure services in Startup.cs è simile a:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<CustomDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
Il modello dei corsi è simile a:
public partial class Courses
{
public Courses()
{
Enrollments = new HashSet<Enrollments>();
}
public int Id { get; set; }
public string Name { get; set; }
public TimeSpan? Duration { get; set; }
public ICollection<Enrollments> Enrollments { get; set; }
}
Il modello delle iscrizioni è simile a:
public partial class Enrollments
{
public string UserId { get; set; }
public int CourseId { get; set; }
public Courses Course { get; set; }
public ApplicationUser User { get; set; }
}
Il modello applicationUser è simile a:
public ApplicationUser()
{
Enrollments = new HashSet<Enrollments>();
}
public ICollection<Enrollments> Enrollments { get; set; }
Ora, ecco quello che ho provato finora:
Se aggiungo il modello Course and Enrollment a ApplicationDBContext, allora tutto va bene.
Se in CustomDBContext ho una chiave primaria non composta, anche allora funziona correttamente. (Ho appena provato un altro esempio)
Qualcuno può per favore chiarire perché è questo errore? È questo il modo previsto per gestire un caso del genere?
Grazie in anticipo.
È perché l'entità Enrollments
è stata scoperta da ApplicationDbContext
tramite la proprietà di navigazione ApplicationUser.Enrollments
. Questo è spiegato nella sezione Tipi di inclusione ed esclusione - Convenzioni della documentazione di EF Core:
Per convenzione, i tipi esposti nelle proprietà
DbSet
nel proprio contesto sono inclusi nel modello. Inoltre, sono inclusi anche i tipi menzionati nel metodoOnModelCreating
. Infine, nel modello sono inclusi anche tutti i tipi trovati esplorando in modo ricorsivo le proprietà di navigazione dei tipi rilevati.
Immagino che ora vedi il problema. Le Enrollments
vengono scoperte e incluse in ApplicationDbContext
, ma non esiste una configurazione fluente per quell'entità, pertanto EF utilizza solo le convenzioni e le annotazioni di dati predefinite. E naturalmente il PK composito richiede una configurazione fluente. E anche se non c'era un PK composito, è ancora scorretto ignorare la configurazione fluente esistente. Nota che i Courses
sono anche inclusi in ApplicationDbContext
dal processo ricorsivo sopra menzionato (attraverso la proprietà di navigazione Enrollments.Courses
). Ecc. Per altre classi di riferimento.
Si noti che lo stesso vale nell'altra direzione. ApplicationUser
e tutti i riferimenti da esso vengono rilevati e inclusi in CustomDbContext
senza la loro fluente configurazione.
La conclusione: non utilizzare contesti separati contenenti entità correlate. Nel tuo caso, metti tutte le entità in ApplicationDBContext
.