đ€ Questions techniques pour entretiens/missions
Q1 : Qu'est-ce qu'Entity Framework Core et pourquoi l'utiliser ?â
Réponse attendue :
- EF Core : ORM (Object-Relational Mapper) qui mappe les objets C# vers des tables SQL
- Avantages : Développement rapide, LINQ, migrations automatiques, change tracking
- Inconvénients : Courbe d'apprentissage, peut générer du SQL non optimal
RĂ©ponse courte : "EF Core transforme vos classes C# en tables SQL et gĂ©nĂšre les requĂȘtes automatiquement"
Q2 : Quelle est la diffĂ©rence entre DbContext et DbSet ?â
Réponse attendue :
public class ShopContext : DbContext // Le contexte = la connexion Ă la DB
{
public DbSet<Product> Products { get; set; } // DbSet = une table
public DbSet<Category> Categories { get; set; }
}
- DbContext : Représente une session avec la base de données, gÚre les connexions et transactions
- DbSet : ReprĂ©sente une collection (table) d'entitĂ©s, permet de faire des requĂȘtes LINQ
Réponse courte : "DbContext = la connexion, DbSet = une table"
Q3 : Comment fonctionnent les migrations EF Core ?â
Réponse attendue :
# Créer une migration (snapshot des changements)
dotnet ef migrations add AddProductTable
# Appliquer les migrations (créer/modifier la DB)
dotnet ef database update
# Supprimer la derniĂšre migration
dotnet ef migrations remove
Explication : Les migrations créent des fichiers C# qui décrivent les changements de schéma. EF Core peut alors créer ou mettre à jour la base de données automatiquement.
Réponse courte : "Les migrations génÚrent et appliquent automatiquement les changements de schéma de base de données"
Q4 : Quelle est la diffĂ©rence entre Include et ThenInclude ?â
Réponse attendue :
// Include : charger la relation directe
var blogs = await context.Blogs
.Include(b => b.Posts) // Charger les posts du blog
.ToListAsync();
// ThenInclude : charger une relation imbriquée
var blogs = await context.Blogs
.Include(b => b.Posts)
.ThenInclude(p => p.Comments) // Charger les commentaires des posts
.ToListAsync();
Réponse courte : "Include pour les relations directes, ThenInclude pour les relations imbriquées"
Q5 : Qu'est-ce que le lazy loading et pourquoi l'Ă©viter ?â
Réponse attendue :
Lazy Loading :
// Avec lazy loading activé
var blog = await context.Blogs.FirstAsync();
var posts = blog.Posts; // RequĂȘte SQL automatique en arriĂšre-plan
ProblĂšme : Peut gĂ©nĂ©rer des centaines de requĂȘtes SQL sans qu'on s'en rende compte (problĂšme N+1)
Solution : Eager Loading avec Include
var blog = await context.Blogs
.Include(b => b.Posts) // Tout charger en une seule requĂȘte
.FirstAsync();
Réponse courte : "Lazy loading charge les données à la demande, mais peut créer des problÚmes de performance (N+1)"
Q6 : Quelle est la diffĂ©rence entre Add, Attach, et Update ?â
Réponse attendue :
// Add : Nouvelle entité à insérer
var product = new Product { Name = "New Product", Price = 99 };
context.Products.Add(product);
await context.SaveChangesAsync(); // INSERT
// Attach : Entité existante non trackée
var product = new Product { Id = 5, Name = "Updated", Price = 150 };
context.Products.Attach(product); // EF Core commence Ă tracker
context.Entry(product).State = EntityState.Modified;
await context.SaveChangesAsync(); // UPDATE
// Update : Marque toute l'entité comme modifiée
var product = new Product { Id = 5, Name = "Updated", Price = 150 };
context.Products.Update(product);
await context.SaveChangesAsync(); // UPDATE tous les champs
Réponse courte : "Add = INSERT, Attach = tracker une entité, Update = UPDATE tous les champs"
Q7 : Comment Ă©viter le problĂšme N+1 avec EF Core ?â
Réponse attendue :
â ProblĂšme N+1 :
var categories = await context.Categories.ToListAsync(); // 1 requĂȘte
foreach (var category in categories)
{
var products = category.Products.ToList(); // N requĂȘtes !
}
â Solution 1 : Include
var categories = await context.Categories
.Include(c => c.Products) // 1 seule requĂȘte avec JOIN
.ToListAsync();
â Solution 2 : Select
var data = await context.Categories
.Select(c => new
{
Category = c,
Products = c.Products
})
.ToListAsync();
RĂ©ponse courte : "Utiliser Include ou Select pour charger toutes les donnĂ©es en une seule requĂȘte"
Q8 : Qu'est-ce qu'AsNoTracking et quand l'utiliser ?â
Réponse attendue :
// Sans AsNoTracking (par défaut)
var products = await context.Products.ToListAsync();
// EF Core track les changements â plus lent
// Avec AsNoTracking
var products = await context.Products
.AsNoTracking() // Plus rapide !
.ToListAsync();
// EF Core ne track pas â idĂ©al pour la lecture seule
Quand utiliser :
- â Affichage de donnĂ©es (read-only)
- â APIs GET qui retournent des donnĂ©es
- â Rapports et exports
- â Quand vous devez modifier les donnĂ©es aprĂšs
Réponse courte : "AsNoTracking désactive le change tracking pour améliorer les performances en lecture seule"
Q9 : Comment gĂ©rer les transactions avec EF Core ?â
Réponse attendue :
using var transaction = await context.Database.BeginTransactionAsync();
try
{
// Opération 1
var order = new Order { CustomerId = 1, Total = 150 };
context.Orders.Add(order);
await context.SaveChangesAsync();
// Opération 2
var payment = new Payment { OrderId = order.Id, Amount = 150 };
context.Payments.Add(payment);
await context.SaveChangesAsync();
// Tout a réussi
await transaction.CommitAsync();
}
catch
{
// Erreur : annuler tout
await transaction.RollbackAsync();
throw;
}
Réponse courte : "BeginTransaction, opérations + SaveChanges, puis Commit ou Rollback"
Q10 : Quelle est la diffĂ©rence entre Find et FirstOrDefault ?â
Réponse attendue :
// Find : cherche par clé primaire, check d'abord le cache
var product = await context.Products.FindAsync(5);
// Avantage : Si l'entitĂ© est dĂ©jĂ trackĂ©e, pas de requĂȘte SQL
// FirstOrDefault : toujours fait une requĂȘte SQL
var product = await context.Products
.FirstOrDefaultAsync(p => p.Id == 5);
// Avantage : Permet des conditions complexes
Quand utiliser quoi :
Find: Recherche simple par ID, peut Ă©viter une requĂȘte SQLFirstOrDefault: Recherche avec conditions complexes ou Include
â ïž Attention avec les soft deletes :
FindAsync n'applique PAS les QueryFilters ! Si vous utilisez des soft deletes, préférez FirstOrDefaultAsync.
// Avec soft delete + QueryFilter
var product = await context.Products.FindAsync(5);
// â ïž Peut retourner un produit supprimĂ© (IsDeleted = true) depuis le cache !
var product = await context.Products.FirstOrDefaultAsync(p => p.Id == 5);
// â
Respecte le QueryFilter, ne retourne pas les supprimés
RĂ©ponse courte : "Find cherche par ID et utilise le cache, FirstOrDefault fait toujours une requĂȘte SQL"
Q11 : Comment configurer une relation many-to-many en EF Core ?â
Réponse attendue :
Approche moderne (EF Core 5+) :
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public List<Course> Courses { get; set; }
}
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public List<Student> Students { get; set; }
}
// EF Core crée automatiquement la table de jointure !
Approche explicite (avec table de jointure) :
public class StudentCourse
{
public int StudentId { get; set; }
public Student Student { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
public DateTime EnrolledDate { get; set; } // Données supplémentaires
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<StudentCourse>()
.HasKey(sc => new { sc.StudentId, sc.CourseId });
}
Réponse courte : "EF Core 5+ gÚre automatiquement les many-to-many, sinon créer une entité de jointure"
Q12 : Comment optimiser une requĂȘte EF Core lente ?â
Réponse pratique :
1. Vérifier le SQL généré
// Activer le logging
optionsBuilder
.UseSqlServer(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information);
// Voir le SQL dans les logs
var products = await context.Products.Where(p => p.Price > 100).ToListAsync();
2. Optimisations courantes
// â Mauvais : rĂ©cupĂ©rer toutes les colonnes
var products = await context.Products.ToListAsync();
// â
Bon : projection (Select) pour récupérer uniquement ce qui est nécessaire
var products = await context.Products
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
// â
Bon : AsNoTracking si lecture seule
var products = await context.Products
.AsNoTracking()
.ToListAsync();
// â
Bon : Pagination
var products = await context.Products
.Skip(20)
.Take(10)
.ToListAsync();
3. Ăviter les problĂšmes frĂ©quents
- Pas d'Include inutiles
- Pas de requĂȘtes en boucle
- Utiliser des index sur les colonnes filtrées
Réponse courte : "Logger le SQL, utiliser Select/AsNoTracking/Pagination, éviter les Include inutiles"
Q13 : Quelle est la diffĂ©rence entre SaveChanges et SaveChangesAsync ?â
Réponse attendue :
// SaveChanges : synchrone (bloque le thread)
context.Products.Add(product);
context.SaveChanges(); // Bloque jusqu'à ce que la DB réponde
// SaveChangesAsync : asynchrone (libĂšre le thread)
context.Products.Add(product);
await context.SaveChangesAsync(); // Le thread peut faire autre chose
Quand utiliser quoi :
SaveChangesAsync: Toujours dans les applications web/API (meilleure scalabilité)SaveChanges: Applications console simples ou scripts
Réponse courte : "SaveChangesAsync est asynchrone et préférable pour les apps web (meilleure performance)"
Q14 : Comment gĂ©rer les soft deletes avec EF Core ?â
Réponse attendue :
Approche 1 : Propriété IsDeleted
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; } // Soft delete
}
// Global query filter
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasQueryFilter(p => !p.IsDeleted); // Filtrer automatiquement
}
// Utilisation
product.IsDeleted = true;
await context.SaveChangesAsync(); // UPDATE, pas DELETE
// Ignorer le filtre si besoin
var allProducts = await context.Products
.IgnoreQueryFilters() // Inclure les supprimés
.ToListAsync();
Réponse courte : "Ajouter une propriété IsDeleted + QueryFilter global pour filtrer automatiquement"
Q15 : Comment tester du code utilisant EF Core ?â
Réponse attendue :
Approche 1 : InMemory Database
var options = new DbContextOptionsBuilder<ShopContext>()
.UseInMemoryDatabase(databaseName: "TestDb")
.Options;
await using var context = new ShopContext(options);
// Ajouter des données de test
context.Products.Add(new Product { Name = "Test", Price = 100 });
await context.SaveChangesAsync();
// Tester
var service = new ProductService(context);
var result = await service.GetExpensiveProductsAsync(50);
Assert.AreEqual(1, result.Count);
Approche 2 : SQLite en mémoire (plus réaliste)
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<ShopContext>()
.UseSqlite(connection)
.Options;
await using var context = new ShopContext(options);
await context.Database.EnsureCreatedAsync();
Réponse courte : "InMemory pour les tests rapides, SQLite en mémoire pour plus de réalisme"
đ Checklist de prĂ©parationâ
Avant un entretien/mission sur EF Core, assurez-vous de pouvoir :
- Expliquer la différence DbContext vs DbSet
- Créer et appliquer des migrations
- Utiliser Include et ThenInclude correctement
- Expliquer et éviter le problÚme N+1
- Utiliser AsNoTracking quand c'est approprié
- Gérer les transactions
- Configurer des relations (1-n, n-n)
- Optimiser une requĂȘte lente
- Tester du code avec EF Core
- Expliquer Find vs FirstOrDefault