DEV Community

Entity Framework Code-First Σχέσεις οντοτήτων μοντελοποίηση και Queries.

🧩 1. Παράδειγμα Μοντέλων

📘 Παράδειγμα Domain:

Στο παρόν παράδειγμα θα κάνουμε χρήση ενός σύστηματος πανεπιστημίου με τις ακόλουθες οντότητες:

_- Student

  • StudentProfile → σχέση 1-1
  • Course → σχέση Ν-Ν (Students ↔ Courses)
  • Department → σχέση 1-Ν (Department ↔ Students)_

Μοντέλα

// Domain/Entities/Student.cs
public class Student
{
    public int Id { get; set; }
    public string FullName { get; set; } = null!;
    public int DepartmentId { get; set; }

    // Relationships
    public Department Department { get; set; } = null!;
    public StudentProfile Profile { get; set; } = null!;
    public ICollection<Course> Courses { get; set; } = new List<Course>();
}
Enter fullscreen mode Exit fullscreen mode

Εξήγηση

Στην κλάση Student εκτός των ιδιωτήτων properties που αφορούν τα χαρακτηριστικά της κλάσης, έχουμε επιπλέον ένα property τύπου Department το οποίο σηματοδοτεί την σχέση του Φοιτητή με το τμήμα, δλδ σε ποιο τμήμα ανήκει ο φοιτητής καθός ένα τμήμα μπορεί να έχει πολλούς φοιτητές αλλά ένας φοιτητής ανήκει σε ένα και μόνο τμήμα.

Το StudentProfile δίχνει την 1 προς 1 σχέση του Φοιτητή με τα εκτεταμένα στοιχεία του.

Και τέλος η συλλογή Courses δίχνει τα μαθήματα που ο φοιτητής παρακολουθεί. Η σχέση αυτή είναι (N-N) πολλά προς πολλά καθώς ένας φοιτητής μπορεί να έχει πολλά μαθήματα και ένα μάθημα μπορεί να το επιλέξουν πολλοί φοιτητές.

// Domain/Entities/StudentProfile.cs
public class StudentProfile
{
    public int Id { get; set; }
    public DateTime BirthDate { get; set; }
    public string Address { get; set; } = null!;

    // 1-1 with Student
    public int StudentId { get; set; }
    public Student Student { get; set; } = null!;
}
Enter fullscreen mode Exit fullscreen mode

Εξήγηση

Το StudentId είναι ξένο κλειδί συσχέτησης με την οντότητα Student.

Για να γίνει η συσχέτηση χρειάζεται και το property τύπου Student. Με αυτά τα δύο ολοκληρώνεται η συσχέτηση των δυο οντοτήτων.

// Domain/Entities/Department.cs
public class Department
{
    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public ICollection<Student> Students { get; set; } = new List<Student>();
}
Enter fullscreen mode Exit fullscreen mode

Εξήγηση

Η Συλλογή Students ικανοποιεί την παραδοχή ότι ένα τμήμα μπορεί να έχει πολλούς μαθητές αλλά ένας μαθητής έχει κάνει εγγραφή και ανήκει σε ένα και μόνο τμήμα. (1-Ν)

// Domain/Entities/Course.cs
public class Course
{
    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public ICollection<Student> Students { get; set; } = new List<Student>();
}

Enter fullscreen mode Exit fullscreen mode

Εξήγηση

Η Συλλογή Students ικανοποιεί την παραδοχή ότι ένα μάθημα μπορεί να το έχουν πολλοί μαθητές και έτσι γνωρίζουμε αυτό το μάθημα ποιοί μαθητές το έχουν πάρει.


Πώς θα στήσουμε το DbContext για την επικοινωνία με τις 4 οντότητες.

// Infrastructure/Persistence/UniversityDbContext.cs
using Microsoft.EntityFrameworkCore;
using Domain.Entities;

public class UniversityDbContext : DbContext
{
    public DbSet<Student> Students => Set<Student>();
    public DbSet<StudentProfile> StudentProfiles => Set<StudentProfile>();
    public DbSet<Department> Departments => Set<Department>();
    public DbSet<Course> Courses => Set<Course>();

    public UniversityDbContext(DbContextOptions<UniversityDbContext> options)
        : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // 1-1 Student ↔ Profile
        modelBuilder.Entity<Student>()
            .HasOne(s => s.Profile)
            .WithOne(p => p.Student)
            .HasForeignKey<StudentProfile>(p => p.StudentId);

        // 1-N Department ↔ Students
        modelBuilder.Entity<Department>()
            .HasMany(d => d.Students)
            .WithOne(s => s.Department)
            .HasForeignKey(s => s.DepartmentId);

        // N-N Student ↔ Course
        modelBuilder.Entity<Student>()
            .HasMany(s => s.Courses)
            .WithMany(c => c.Students)
            .UsingEntity(j => j.ToTable("StudentCourses"));
    }
}

Enter fullscreen mode Exit fullscreen mode

Εξήγηση

// 1-1 Student ↔ Profile
modelBuilder.Entity<Student>()
.HasOne(s => s.Profile)
.WithOne(p => p.Student)
.HasForeignKey<StudentProfile>(p => p.StudentId);

Ένας φοιτητής έχει ένα προφίλ με τα αναλυτικά του στοιχεία και ένα προφίλ ανήκει σε έναν και μόνο φοιτητή.

// 1-N Department ↔ Students
modelBuilder.Entity<Department>()
.HasMany(d => d.Students)
.WithOne(s => s.Department)
.HasForeignKey(s => s.DepartmentId);

Ένα τμήμα έχει πολούς μαθητές και ένας μαθητής φοιτεί σε ένα και μόνον τμήμα.

// N-N Student ↔ Course
modelBuilder.Entity<Student>()
.HasMany(s => s.Courses)
.WithMany(c => c.Students)
.UsingEntity(j => j.ToTable("StudentCourses"));

Ένα μάθημα μπορεί να το παρακολουθήσουν πολλοί γοιτητές και ένας φοιτητής μπορεί να παρακολουθεί πολλά μαθήματα. Για την σχέση αυτή βλεπουμε πως δημιουργείτε ένας ανδιάμεσος πίνακας ο οποίος κρατά τα ξένα κλειδιά για γνωρίζουμε ποιός φοιτητής παρακολουθεί ποιά μαθήματα.


1️⃣ Τι είναι το ModelBuilder και πώς δουλεύει

🔹 Ρόλος:
Το ModelBuilder είναι το αντικείμενο που σου δίνει το EF Core μέσα στη μέθοδο OnModelCreating του DbContext.

  • Μέσω αυτού μπορείς να διαμορφώσεις (configure) το μοντέλο σου, δηλαδή:
  • Πώς χαρτογραφούνται τα classes → σε tables
  • Ποιες είναι οι σχέσεις (1-1, 1-Ν, Ν-Ν)
  • Ποιο είναι το primary key / foreign key
  • Πώς λέγονται οι πίνακες και οι στήλες
  • Indexes, constraints, default values, precision κ.λπ.

🔹 Πώς δουλεύει στην πράξη

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 1️⃣ Πίνακας & Primary key
    modelBuilder.Entity<Student>()
        .ToTable("Students")
        .HasKey(s => s.Id);

    // 2️⃣ Required property & column name
    modelBuilder.Entity<Student>()
        .Property(s => s.FullName)
        .IsRequired()
        .HasColumnName("Full_Name");

    // 3️⃣ One-to-One
    modelBuilder.Entity<Student>()
        .HasOne(s => s.Profile)
        .WithOne(p => p.Student)
        .HasForeignKey<StudentProfile>(p => p.StudentId);

    // 4️⃣ One-to-Many
    modelBuilder.Entity<Department>()
        .HasMany(d => d.Students)
        .WithOne(s => s.Department)
        .HasForeignKey(s => s.DepartmentId);

    // 5️⃣ Many-to-Many
    modelBuilder.Entity<Student>()
        .HasMany(s => s.Courses)
        .WithMany(c => c.Students)
        .UsingEntity(j => j.ToTable("StudentCourses"));
}

Enter fullscreen mode Exit fullscreen mode

💡 Ο ModelBuilder χρησιμοποιεί Fluent API, δηλαδή αλυσιδωτές μεθόδους (chain methods), ώστε να διαμορφώνεις δηλωτικά τις σχέσεις και τα mappings.
Αν δεν τον χρησιμοποιήσεις, το EF κάνει convention-based mapping (βασισμένο στα property names).

Εξήγηση

// 1️⃣ Πίνακας & Primary key
modelBuilder.Entity<Student>()
    .ToTable("Students")
    .HasKey(s => s.Id);
Enter fullscreen mode Exit fullscreen mode

Δήλωση ονόματος πίνακα και πίνακα καθώς και Primary key

// 2️⃣ Required property & column name
modelBuilder.Entity()
.Property(s => s.FullName)
.IsRequired()
.HasColumnName("Full_Name");

Δήλωση ότι απαιτείται το FullName και έχει το όνομα Full_Name

// 3️⃣ One-to-One
modelBuilder.Entity<Student>()
    .HasOne(s => s.Profile)
    .WithOne(p => p.Student)
    .HasForeignKey<StudentProfile>(p => p.StudentId);
Enter fullscreen mode Exit fullscreen mode

Δήλωση σχέσης πικάνων και ξένο κλειδί

// 4️⃣ One-to-Many
modelBuilder.Entity()
.HasMany(d => d.Students)
.WithOne(s => s.Department)
.HasForeignKey(s => s.DepartmentId);

Δήλωση σχέσης πικάνων και ξένο κλειδί

// 5️⃣ Many-to-Many
modelBuilder.Entity<Student>()
    .HasMany(s => s.Courses)
    .WithMany(c => c.Students)
    .UsingEntity(j => j.ToTable("StudentCourses"));
Enter fullscreen mode Exit fullscreen mode

_Δήλωση σχέσης πικάνων και πίνακα > StudentCourses για την διατήρηση των ξάνων κλειδιών στη σχέση (Ν-Ν) _


Migration (ενδεικτικά)

Στο terminal:

dotnet ef migrations add InitialCreate
dotnet ef database update
Enter fullscreen mode Exit fullscreen mode

🧱 2️⃣ CRUD Ενέργειες (Create, Read, Update, Delete)

Ακολουθούν παραδείγματα για κάθε entity χρησιμοποιώντας DbContext και async/await.

🔹 CREATE (Εισαγωγή)

var department = new Department { Name = "Computer Science" };
context.Departments.Add(department);
await context.SaveChangesAsync();

var student = new Student
{
    FullName = "John Doe",
    DepartmentId = department.Id,
    Profile = new StudentProfile
    {
        BirthDate = new DateTime(2000, 1, 1),
        Address = "Athens"
    }
};
context.Students.Add(student);
await context.SaveChangesAsync();

var course = new Course { Title = "Databases" };
context.Courses.Add(course);
await context.SaveChangesAsync();

// Many-to-Many association
student.Courses.Add(course);
await context.SaveChangesAsync();

Enter fullscreen mode Exit fullscreen mode

🔹 READ (Ανάγνωση)

// Όλοι οι φοιτητές
var students = await context.Students.ToListAsync();

// Με Include (φόρτωμα σχέσεων)
var studentsWithDepartment = await context.Students
    .Include(s => s.Department)
    .Include(s => s.Profile)
    .Include(s => s.Courses)
    .ToListAsync();

// Εύρεση φοιτητή με ID
var student = await context.Students
    .Include(s => s.Profile)
    .Include(s => s.Department)
    .FirstOrDefaultAsync(s => s.Id == 1);

Enter fullscreen mode Exit fullscreen mode

🔹 UPDATE (Ενημέρωση)

var student = await context.Students.FirstAsync();
student.FullName = "Johnathan Doe";
student.Profile.Address = "Thessaloniki";
await context.SaveChangesAsync();

Enter fullscreen mode Exit fullscreen mode

🔹 DELETE (Διαγραφή)

var student = await context.Students.FirstAsync();
context.Students.Remove(student);
await context.SaveChangesAsync();

Enter fullscreen mode Exit fullscreen mode

3️⃣ Παραδείγματα Ερωτημάτων (LINQ / SQL-Style)

3.1 — Όλοι οι φοιτητές και το τμήμα τους

var query = await context.Students
    .Include(s => s.Department)
    .Select(s => new
    {
        StudentName = s.FullName,
        Department = s.Department.Name
    })
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

3.2 — Όλα τα μαθήματα που παρακολουθεί ένας φοιτητής

int studentId = 1;
var courses = await context.Students
    .Where(s => s.Id == studentId)
    .SelectMany(s => s.Courses)
    .Select(c => c.Title)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

ή

var studentWithCourses = await context.Students
    .Include(s => s.Courses)
    .FirstOrDefaultAsync(s => s.Id == studentId);

foreach (var course in studentWithCourses.Courses)
    Console.WriteLine(course.Title);

Enter fullscreen mode Exit fullscreen mode

3.3 — Ποιοι φοιτητές είναι σε κάθε τμήμα

var departments = await context.Departments
    .Include(d => d.Students)
    .Select(d => new
    {
        Department = d.Name,
        Students = d.Students.Select(s => s.FullName)
    })
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

3.4 — Όλοι οι φοιτητές με πληροφορίες από προφίλ και μαθήματα

var data = await context.Students
    .Include(s => s.Profile)
    .Include(s => s.Department)
    .Include(s => s.Courses)
    .Select(s => new
    {
        s.FullName,
        s.Profile.Address,
        s.Profile.BirthDate,
        Department = s.Department.Name,
        Courses = s.Courses.Select(c => c.Title)
    })
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

3.5 — Raw SQL (αν θέλεις να γράψεις SQL απευθείας)

var students = await context.Students
    .FromSqlRaw("SELECT * FROM Students WHERE DepartmentId = {0}", 1)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

4️⃣ Παράδειγμα Σύνθετου Σεναρίου

Βρες τον φοιτητή, τα μαθήματα του και το τμήμα του σε μία LINQ ερώτηση

int studentId = 2;

var studentDetails = await context.Students
    .Where(s => s.Id == studentId)
    .Select(s => new
    {
        s.FullName,
        Department = s.Department.Name,
        Courses = s.Courses.Select(c => c.Title)
    })
    .FirstOrDefaultAsync();

Console.WriteLine($"Φοιτητής: {studentDetails.FullName}");
Console.WriteLine($"Τμήμα: {studentDetails.Department}");
Console.WriteLine("Μαθήματα:");
foreach (var course in studentDetails.Courses)
    Console.WriteLine($" - {course}");
Enter fullscreen mode Exit fullscreen mode

⚠️ Προσοχή: το EF κάνει parameter binding, μην βάζεις raw string concatenation (SQL injection).

5️⃣ Χρήσιμες Εντολές EF CLI

# Δημιουργία migration
dotnet ef migrations add InitialCreate

# Ενημέρωση βάσης
dotnet ef database update

# Διαγραφή βάσης (όταν θες restart)
dotnet ef database drop

# Δημιουργία SQL script
dotnet ef migrations script

Enter fullscreen mode Exit fullscreen mode

Ας δούμε κάποια επιπλέον ερωτήματα

6️⃣ Include / ThenInclude (Eager Loading)
👉 Φέρε όλους τους φοιτητές με το προφίλ και το τμήμα τους

var students = await context.Students
    .Include(s => s.Profile)
    .Include(s => s.Department)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Φέρε όλα τα τμήματα με τους φοιτητές τους και τα προφίλ των φοιτητών

var departments = await context.Departments
    .Include(d => d.Students)
        .ThenInclude(s => s.Profile)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Φέρε όλους τους φοιτητές με τα μαθήματα και το προφίλ

var studentsWithCourses = await context.Students
    .Include(s => s.Courses)
    .Include(s => s.Profile)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Φέρε φοιτητές ενός συγκεκριμένου τμήματος και τα μαθήματα τους

int departmentId = 1;

var students = await context.Students
    .Where(s => s.DepartmentId == departmentId)
    .Include(s => s.Courses)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Φέρε πόσοι φοιτητές υπάρχουν ανά τμήμα

var studentCounts = await context.Students
    .GroupBy(s => s.Department.Name)
    .Select(g => new
    {
        Department = g.Key,
        Count = g.Count()
    })
    .OrderByDescending(x => x.Count)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Ποιοι φοιτητές έχουν πάνω από 3 μαθήματα

var students = await context.Students
    .Where(s => s.Courses.Count > 3)
    .Include(s => s.Courses)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Μέσος όρος ηλικίας φοιτητών ανά τμήμα

var averageAges = await context.Students
    .Include(s => s.Profile)
    .GroupBy(s => s.Department.Name)
    .Select(g => new
    {
        Department = g.Key,
        AverageAge = g.Average(s => EF.Functions.DateDiffYear(s.Profile.BirthDate, DateTime.Now))
    })
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Φέρε πλήρη καρτέλα φοιτητή

int studentId = 2;

var studentCard = await context.Students
    .Where(s => s.Id == studentId)
    .Include(s => s.Profile)
    .Include(s => s.Department)
    .Include(s => s.Courses)
    .Select(s => new
    {
        s.FullName,
        Department = s.Department.Name,
        Address = s.Profile.Address,
        BirthDate = s.Profile.BirthDate,
        Courses = s.Courses.Select(c => c.Title)
    })
    .FirstOrDefaultAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Λίστα με φοιτητές και τον αριθμό μαθημάτων τους

var studentCourses = await context.Students
    .Select(s => new
    {
        s.FullName,
        CourseCount = s.Courses.Count
    })
    .OrderByDescending(x => x.CourseCount)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Όλα τα μαθήματα με φοιτητές που τα παρακολουθούν

var courseList = await context.Courses
    .Include(c => c.Students)
    .Select(c => new
    {
        c.Title,
        Students = c.Students.Select(s => s.FullName)
    })
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Όλοι οι φοιτητές ενός μαθήματος

string courseTitle = "Databases";

var students = await context.Courses
    .Where(c => c.Title == courseTitle)
    .SelectMany(c => c.Students)
    .Include(s => s.Department)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Join φοιτητών και τμημάτων (χωρίς navigation property)

var joinResult = await (from s in context.Students
                        join d in context.Departments on s.DepartmentId equals d.Id
                        select new
                        {
                            StudentName = s.FullName,
                            DepartmentName = d.Name
                        }).ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Join φοιτητών και μαθημάτων μέσω πίνακα συνδέσμου

var studentCourses = await (from s in context.Students
                            from c in s.Courses
                            select new
                            {
                                Student = s.FullName,
                                Course = c.Title
                            }).ToListAsync();

Enter fullscreen mode Exit fullscreen mode

👉 Soft delete φοιτητών

// Entity
public class Student
{
    public int Id { get; set; }
    public string FullName { get; set; } = null!;
    public bool IsDeleted { get; set; } = false;
}

// Global Filter
modelBuilder.Entity<Student>()
    .HasQueryFilter(s => !s.IsDeleted);

Enter fullscreen mode Exit fullscreen mode

👉 Εκτέλεση stored procedure ή raw SQL query

var result = await context.Students
    .FromSqlRaw("EXEC GetStudentsByDepartment @p0", 1)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode
student.IsDeleted = true;
await context.SaveChangesAsync();
// Αυτόματα θα φιλτραριστεί από τα queries

Enter fullscreen mode Exit fullscreen mode

Φέρε όλους τους φοιτητές που παρακολουθούν ΜΑΘΗΜΑ στο οποίο είναι πάνω από 5 φοιτητές

var busyCoursesStudents = await context.Students
    .Where(s => s.Courses.Any(c => c.Students.Count > 5))
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

Φέρε φοιτητές που είναι στο ίδιο τμήμα με κάποιον συγκεκριμένο φοιτητή

int studentId = 3;
var departmentId = await context.Students
    .Where(s => s.Id == studentId)
    .Select(s => s.DepartmentId)
    .FirstAsync();

var classmates = await context.Students
    .Where(s => s.DepartmentId == departmentId && s.Id != studentId)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

Φέρε τμήματα που δεν έχουν φοιτητές

var emptyDepartments = await context.Departments
    .Where(d => !d.Students.Any())
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

Φέρε φοιτητές χωρίς προφίλ

var studentsWithoutProfile = await context.Students
    .Where(s => s.Profile == null)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

Top 5 φοιτητές με τα περισσότερα μαθήματα

var topStudents = await context.Students
    .OrderByDescending(s => s.Courses.Count)
    .Take(5)
    .Select(s => new { s.FullName, CourseCount = s.Courses.Count })
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

7️⃣ Advanced Eager Loading & Performance Tips

Σενάριο Παράδειγμα Σχόλιο
Επίπεδο include .Include(s => s.Courses) Φέρνει N:N σχέσεις
Πολλαπλό επίπεδο .Include(s => s.Courses).ThenInclude(c => c.Students) Nested includes
Select για optimization .Select(s => new { s.FullName, Courses = s.Courses.Select(c => c.Title) }) Επιστρέφεις μόνο τα πεδία που χρειάζεσαι
No Tracking .AsNoTracking() Για read-only queries (βελτιώνει performance)
Split Queries .AsSplitQuery() Αποφεύγει Cartesian explosion στα includes

Κατέβασε από το git το solution Clean Architecture .NET 9 με SQL Server και Swagger. Download the solution (UniversitySolution.zip)

Τι περιλαμβάνει το πακέτο

  • src/Domain — Entities (Student, StudentProfile, Department, Course)
  • src/Application — Interfaces & StudentService
  • src/Infrastructure — UniversityDbContext, repository, EF Core packages (SqlServer)
  • src/WebApi — Web API με StudentsController, Program.cs, appsettings.json και Swagger
  • Seed data στο OnModelCreating (μερικοί initial records)
  • README με οδηγίες

Τι να κάνεις μετά (βήμα-βήμα)

  1. Αποσυμπίεσε / άνοιξε το zip.

  2. Άλλαξε τη σύνδεση στη src/WebApi/appsettings.json στο DefaultConnection για να δείχνει τη SQL Server instance σου.
    Παράδειγμα:
    "Server=localhost;Database=UniversityDb;Trusted_Connection=True;"

  3. Από το root του project:

    • dotnet restore
    • dotnet build
  4. Δημιούργησε το migration και ενημέρωσε τη βάση:

    • dotnet ef migrations add InitialCreate --project src/Infrastructure --startup-project src/WebApi
    • dotnet ef database update --project src/Infrastructure --startup-project src/WebApi
  5. Τρέξε το API:

    • dotnet run --project src/WebApi
  6. Άνοιξε Swagger UI (σε Development): https://localhost:5001/swagger ή κατάλληλο URL από το console.


Σημειώσεις & περιορισμοί

  1. Χρησιμοποίησα EF Core 8 package versions that match .NET 9 usage; μπορείς να αναβαθμίσεις αν χρειάζεσαι.

  2. Η seeding των many-to-many rows σε implicit join table έχει placeholder — αν θέλεις να περάσω και συγκεκριμένα seed entries για StudentCourses, μπορώ να προσθέσω ρητό join entity (π.χ. StudentCourse) και να σεeding-άρω πλήρως.

  3. Αν θες, προσθέσε επίσης:

  • DTOs + AutoMapper
  • Repository pattern με generic base
  • Integration tests / xUnit project
  • Example Postman collection

Πηγές Entity Framework Tutorial

Δείτε συμπληρωματικά Εισαγωγή στα SQL JOINs με Παραδείγματα

Top comments (0)