Skip to content

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 语句
  • 状态包括:DetachedUnchangedAddedModifiedDeleted
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 的性能优化建议?

  1. ✅ 使用 AsNoTracking() 进行只读查询
  2. ✅ 避免 N+1 查询,使用 Include
  3. ✅ 使用投影查询只返回需要的字段
  4. ✅ 批量操作使用原生 SQL
  5. ✅ 启用查询分割(Split Query)
  6. ✅ 使用编译查询(Compiled Queries)
  7. ❌ 避免在循环中调用 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();
    }
}

八、最佳实践

  1. 使用 Fluent API 代替 Data Annotations(配置更强大)
  2. 分离查询和命令 - CQRS 模式
  3. 使用仓储模式 封装数据访问逻辑
  4. 启用连接池 - 默认已启用
  5. 使用异步方法 - ToListAsync()SaveChangesAsync()
  6. 合理使用索引 - 在常查询字段上添加索引
  7. 使用事务 - 保证数据一致性
  8. 批量操作 - 使用 AddRangeBulkInsert
  9. 使用编译查询 - 提升查询性能(重复查询)
  10. 启用查询分割 - 避免笛卡尔积问题
  11. 监控查询性能 - 使用日志或诊断工具
  12. 避免过度使用延迟加载 - 可能导致 N+1 问题
  13. 不要在生产环境自动迁移 - 使用脚本手动控制
  14. 避免在循环中调用 SaveChanges - 性能差
  15. 不要忽略异常处理 - 数据库操作可能失败

八、高级特性

全局查询过滤器

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; }
}

基于 VitePress 构建 | Copyright © 2026-present