EF Core 面试题
一、什么是 EF Core?
Entity Framework Core (EF Core) 是微软推出的轻量级、跨平台、可扩展的 ORM(对象关系映射)框架。
核心特性
- 🔄 Code First - 从代码生成数据库
- 🗄️ Database First - 从数据库生成代码(通过 Scaffold-DbContext)
- 🔍 LINQ 查询 - 使用强类型语言集成查询
- 🎯 变更跟踪 - 自动追踪实体变化
- 💾 迁移 - 管理数据库架构版本
二、基础使用
1. 定义实体模型
csharp
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}2. 配置 DbContext
csharp
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("连接字符串");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Fluent API 配置
modelBuilder.Entity<Blog>()
.HasMany(b => b.Posts)
.WithOne(p => p.Blog)
.HasForeignKey(p => p.BlogId);
}
}3. 基本 CRUD 操作
csharp
using var context = new BlogContext();
// 查询
var blogs = await context.Blogs
.Where(b => b.Name.Contains("技术"))
.ToListAsync();
// 添加
var newBlog = new Blog { Name = "新博客" };
context.Blogs.Add(newBlog);
await context.SaveChangesAsync();
// 更新
var blog = await context.Blogs.FindAsync(1);
blog.Name = "更新后的名称";
await context.SaveChangesAsync();
// 删除
context.Blogs.Remove(blog);
await context.SaveChangesAsync();三、查询优化
1. 延迟加载 vs 立即加载
csharp
// ❌ N+1 查询问题
var blogs = context.Blogs.ToList();
foreach (var blog in blogs)
{
// 每次循环都会触发一次数据库查询
Console.WriteLine(blog.Posts.Count);
}
// ✅ 使用 Include 预加载
var blogs = context.Blogs
.Include(b => b.Posts)
.ToList();2. AsNoTracking 提升性能
csharp
// 只读查询,不需要变更跟踪
var blogs = await context.Blogs
.AsNoTracking()
.ToListAsync();3. 投影查询
csharp
// ✅ 只查询需要的字段
var blogNames = await context.Blogs
.Select(b => new { b.Id, b.Name })
.ToListAsync();四、关系配置
一对多关系
csharp
modelBuilder.Entity<Blog>()
.HasMany(b => b.Posts)
.WithOne(p => p.Blog)
.HasForeignKey(p => p.BlogId)
.OnDelete(DeleteBehavior.Cascade);多对多关系
csharp
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(t => t.Posts)
.UsingEntity(j => j.ToTable("PostTags"));一对一关系
csharp
modelBuilder.Entity<User>()
.HasOne(u => u.Profile)
.WithOne(p => p.User)
.HasForeignKey<UserProfile>(p => p.UserId);五、迁移
创建迁移
bash
dotnet ef migrations add InitialCreate应用迁移
bash
dotnet ef database update回滚迁移
bash
dotnet ef database update PreviousMigrationName六、常见面试题
Q1: EF Core 的跟踪机制是什么?
变更跟踪(Change Tracking):
- EF Core 会追踪查询到的实体状态
- 调用
SaveChanges()时自动生成 UPDATE/DELETE 语句 - 状态包括:
Detached、Unchanged、Added、Modified、Deleted
csharp
var blog = await context.Blogs.FindAsync(1);
blog.Name = "新名称"; // EF Core 自动追踪此变化
await context.SaveChangesAsync(); // 生成 UPDATE 语句Q2: Include 和 ThenInclude 的区别?
csharp
// Include:加载第一层导航属性
var blogs = context.Blogs
.Include(b => b.Posts)
.ToList();
// ThenInclude:加载更深层级的导航属性
var blogs = context.Blogs
.Include(b => b.Posts)
.ThenInclude(p => p.Comments)
.ToList();Q3: 如何处理并发冲突?
使用并发令牌(Concurrency Token):
csharp
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
// 处理并发冲突
var entry = ex.Entries.Single();
var databaseValues = await entry.GetDatabaseValuesAsync();
// 合并或重试
}Q4: EF Core 的性能优化建议?
- ✅ 使用
AsNoTracking()进行只读查询 - ✅ 避免 N+1 查询,使用
Include - ✅ 使用投影查询只返回需要的字段
- ✅ 批量操作使用原生 SQL
- ✅ 启用查询分割(Split Query)
- ✅ 使用编译查询(Compiled Queries)
- ❌ 避免在循环中调用
SaveChangesAsync()
csharp
// ❌ 错误:循环中保存
foreach (var item in items)
{
context.Add(item);
await context.SaveChangesAsync();
}
// ✅ 正确:批量保存
context.AddRange(items);
await context.SaveChangesAsync();Q5: 如何执行原生 SQL?
csharp
// 查询
var blogs = await context.Blogs
.FromSqlRaw("SELECT * FROM Blogs WHERE Name LIKE {0}", "%技术%")
.ToListAsync();
// 使用参数化查询(防止 SQL 注入)
var blogs = await context.Blogs
.FromSqlRaw("SELECT * FROM Blogs WHERE Name LIKE {0} AND Status = {1}",
"%技术%", 1)
.ToListAsync();
// 使用 FromSqlInterpolated(推荐,自动参数化)
var searchTerm = "%技术%";
var blogs = await context.Blogs
.FromSqlInterpolated($"SELECT * FROM Blogs WHERE Name LIKE {searchTerm}")
.ToListAsync();
// 执行命令
await context.Database
.ExecuteSqlRawAsync("UPDATE Blogs SET Name = {0} WHERE Id = {1}",
"新名称", 1);
// 执行存储过程
var blogs = await context.Blogs
.FromSqlRaw("EXEC GetBlogsByCategory @CategoryId = {0}", categoryId)
.ToListAsync();七、实际应用场景
1. 分页查询
csharp
public async Task<PagedResult<Blog>> GetBlogsAsync(int page, int pageSize)
{
var total = await context.Blogs.CountAsync();
var blogs = await context.Blogs
.AsNoTracking()
.OrderByDescending(b => b.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return new PagedResult<Blog>
{
Items = blogs,
Total = total,
Page = page,
PageSize = pageSize
};
}2. 事务处理
csharp
using var transaction = await context.Database.BeginTransactionAsync();
try
{
// 多个操作
context.Blogs.Add(newBlog);
context.Posts.AddRange(newPosts);
await context.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}3. 仓储模式实现
csharp
public interface IRepository<T> where T : class
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
}
public class Repository<T> : IRepository<T> where T : class
{
protected readonly DbContext _context;
protected readonly DbSet<T> _dbSet;
public Repository(DbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T> GetByIdAsync(int id)
{
return await _dbSet.FindAsync(id);
}
public async Task<IEnumerable<T>> GetAllAsync()
{
return await _dbSet.ToListAsync();
}
public async Task AddAsync(T entity)
{
await _dbSet.AddAsync(entity);
await _context.SaveChangesAsync();
}
public async Task UpdateAsync(T entity)
{
_dbSet.Update(entity);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(T entity)
{
_dbSet.Remove(entity);
await _context.SaveChangesAsync();
}
}4. Unit of Work 模式
csharp
public interface IUnitOfWork : IDisposable
{
IRepository<Blog> Blogs { get; }
IRepository<Post> Posts { get; }
Task<int> SaveChangesAsync();
}
public class UnitOfWork : IUnitOfWork
{
private readonly DbContext _context;
public UnitOfWork(DbContext context)
{
_context = context;
Blogs = new Repository<Blog>(_context);
Posts = new Repository<Post>(_context);
}
public IRepository<Blog> Blogs { get; }
public IRepository<Post> Posts { get; }
public async Task<int> SaveChangesAsync()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context?.Dispose();
}
}八、最佳实践
- ✅ 使用 Fluent API 代替 Data Annotations(配置更强大)
- ✅ 分离查询和命令 - CQRS 模式
- ✅ 使用仓储模式 封装数据访问逻辑
- ✅ 启用连接池 - 默认已启用
- ✅ 使用异步方法 -
ToListAsync()、SaveChangesAsync() - ✅ 合理使用索引 - 在常查询字段上添加索引
- ✅ 使用事务 - 保证数据一致性
- ✅ 批量操作 - 使用
AddRange、BulkInsert等 - ✅ 使用编译查询 - 提升查询性能(重复查询)
- ✅ 启用查询分割 - 避免笛卡尔积问题
- ✅ 监控查询性能 - 使用日志或诊断工具
- ❌ 避免过度使用延迟加载 - 可能导致 N+1 问题
- ❌ 不要在生产环境自动迁移 - 使用脚本手动控制
- ❌ 避免在循环中调用 SaveChanges - 性能差
- ❌ 不要忽略异常处理 - 数据库操作可能失败
八、高级特性
全局查询过滤器
csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasQueryFilter(b => !b.IsDeleted);
}值转换器
csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Property(u => u.Email)
.HasConversion(
v => v.ToLowerInvariant(),
v => v
);
}拥有实体类型
csharp
public class Order
{
public int Id { get; set; }
public Address ShippingAddress { get; set; }
}
[Owned]
public class Address
{
public string Street { get; set; }
public string City { get; set; }
}