Many-To-Many
FYI: Para o exemplo que será apresentado aqui estou utilizando build noturno e você pode também instalar esses pacotes em seu projeto, assim você sempre terá a ultima compilação do projeto, para testar todas funcionalidades novas que estão sendo implementadas, veja aqui o que você precisa fazer.
EF Core 3.1
Cenário
Como funcionava no EF Core 3.1?
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public IList<CourseStudent> CourseStudents { get; } = new List<CourseStudent>();
}
public class Course
{
public int Id { get; set; }
public string Description { get; set; }
public IList<CourseStudent> CourseStudents { get; } = new List<CourseStudent>();
}
public class CourseStudent
{
public int CourseId { get; set; }
public Course Course { get; set; }
public int StudentId { get; set; }
public Student Student { get; set; }
}
Work around
public class SampleManyToManyContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Course> Course { get; set; }
// Configure Explicit
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CourseStudent>()
.HasKey(p => new { p.CourseId, p.StudentId });
modelBuilder.Entity<CourseStudent>()
.HasOne(p => p.Student)
.WithMany(p => p.CourseStudents)
.HasForeignKey(p => p.StudentId);
modelBuilder.Entity<CourseStudent>()
.HasOne(p => p.Course)
.WithMany(p => p.CourseStudents)
.HasForeignKey(p => p.CourseId);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer("Data source=(localdb)\\mssqllocaldb;Initial Catalog=SampleManyToMany31;Integrated Security=true");
}
System.InvalidOperationException: 'The entity type 'CourseStudent' requires a primary key to be defined.
If you intended to use a keyless entity type call 'HasNoKey()'.'
Equipe
E agora como ficou?
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Course> Courses { get; } = new List<Course>();
}
public class Course
{
public int Id { get; set; }
public string Description { get; set; }
public IList<Student> Students { get; } = new List<Student>();
}
Student agora tem a lista de Courses e não mais a lista de uma terceira classe, da mesma forma Course agora tem a lista de Students, isso
faz muito mais sentido.
Veja também como nosso contexto ficou muito mais simples, basta apenas expor as entitdades em uma propriedade DbSet da seguinte forma:
public class SampleManyToManyContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Course> Course { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer("Data source=(localdb)\\mssqllocaldb;Initial Catalog=SampleManyToMany5;Integrated Security=true");
}
Ficou muito simples não é?!
O Entity Framework Core agora é capaz de fazer o mapeamento correto apenas expondo nossas entidades em nosso contexto, observe que não precisei configurar nada
no exemplo acima, isso porque o Entity Framework Core por conversão já faz todo mapemento pra gente de forma automatizada.
Mepeamento Explícito
Eu sou capaz de fazer essa junção de tabelas explicitamente?
A resposta é sim, e é muito simples de fazer isso, você pode fazer de 2 formas, a primeira é expondo uma propriedade DbSet em seu contexto
veja um exemplo, onde eu criei mais uma classe CourseStudent que contém algumas propriedades adicionais como Protocol e CreatedAt:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Course> Courses { get; } = new List<Course>();
}
public class Course
{
public int Id { get; set; }
public string Description { get; set; }
public IList<Student> Students { get; } = new List<Student>();
}
[Keyless]
public class CourseStudent
{
public int CourseId { get; set; }
public int StudentId { get; set; }
public string Protocol { get; set; }
public DateTime CreatedAt { get; set; }
}
public class SampleManyToManyContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<CourseStudent> CourseStudents { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.EnableSensitiveDataLogging() // Show Data
.LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuted })
.UseSqlServer("Data source=(localdb)\\mssqllocaldb;Initial Catalog=SampleManyToManyExplicit5;Integrated Security=true");
}
A segunda forma de fazer é usando Fluent API você pode fazer o mapeamento explicitamente, fazendo a junção de suas entidades, observe que agora eu tenho um novo método de extensão para fazer isso que é o UsingEntity vejo um exemplo completo:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Course> Courses { get; } = new List<Course>();
}
public class Course
{
public int Id { get; set; }
public string Description { get; set; }
public IList<Student> Students { get; } = new List<Student>();
}
public class CourseStudent
{
public int CourseId { get; set; }
public int StudentId { get; set; }
public string Protocol { get; set; }
public DateTime CreatedAt { get; set; }
}
public class SampleManyToManyContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure Explicit
modelBuilder
.Entity<Student>()
.HasMany(p => p.Courses)
.WithMany(p => p.Students)
.UsingEntity<CourseStudent>(
p => p.HasOne<Course>().WithMany(),
p => p.HasOne<Student>().WithMany());
modelBuilder
.Entity<CourseStudent>(p =>
{
p.Property(e => e.Protocol).HasColumnType("VARCHAR(32)");
p.Property(e => e.CreatedAt).HasDefaultValueSql("GETDATE()");
});
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.EnableSensitiveDataLogging() // Show Data
.LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuted })
.UseSqlServer("Data source=(localdb)\\mssqllocaldb;Initial Catalog=SampleManyToManyExplicit5;Integrated Security=true");
}
static void Main(string[] args)
{
using var db = new SampleManyToManyContext();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var students = db.Students.Include(p => p.Courses).ToList();
var courses = db.Courses.Include(p => p.Students).ToList();
var courseStudent = db.Set<CourseStudent>().FirstOrDefault();
var protocol = courseStudent.Protocol;
var createdAt = courseStudent.CreatedAt;
}
Links
Many-To-Many está sendo rastreado em:
Issue-10508
Issue-1368
Build noturno clique aqui
Exemplos apresentados aqui
Deixe um comentário