如何合理重构EF Core中从数据库获取数据的代码?
原代码
public class Clients { public Id { get; set; } // 缺失类型定义,比如int Id { get; set; } public string Name { get; set; } public string Address { get; set; } } public interface IClientsRepository { Task<List<Clients>> ListAllClients(); } public class ClientsRepository : IClientsRepository { private List<Clients> _clientsList; public ClientsRepository(MainDbContext dbContext) : base(dbContext) { // 假设父类已处理_dbContext注入 } public async Task<List<Clients>> ListAllClients() { _clientsList = new List<Clients>(); var query = await _dbContext.ClientsTbl .AsQueryable() // 多余,DbSet本身就是IQueryable类型 .AsNoTracking() .Join(_dbContext.AddressTbl.AsNoTracking(), client => client.Id, address => address.ClientId, (client, address) => new { client.Id, client.Name, address.FullAddress }) .ToListAsync(); var allClients = query .Select(s => new Clients { Id = s.Id, Name= s.Name, Address = s.FullAddress }); return _clientsList; // 严重bug:返回空列表,完全没用到查询结果allClients } }
问题1:IQueryable关联表的正确方式
纠正误区:IQueryable完全支持Join操作,EF Core的DbSet<T>本身就是IQueryable<T>,你之前的Join写法本来就是基于IQueryable的,只是后续用ToListAsync()把它转换成了内存中的List(IEnumerable)。
如果想让接口返回延迟执行的IQueryable,不需要定义成Task<IQueryable<Clients>>——IQueryable是查询表达式,不是执行结果,不需要异步包装。正确接口定义:
public interface IClientsRepository { IQueryable<Clients> ListAllClients(); }
调用方可以后续追加过滤、排序条件,最后再用ToListAsync()执行查询,完全没必要把List强制转成IQueryable(这种做法毫无意义,数据已经加载到内存,转IQueryable也不会再走数据库)。
问题2:构建延迟执行的表达式树(避免立即查询)
你遇到的错误是因为await只能用于Task类型,而IQueryable不是Task。要构建延迟执行的查询,直接返回IQueryable即可,不需要await,也不需要ToListAsync()。
延迟查询写法示例:
public IQueryable<Clients> ListAllClients() { return _dbContext.ClientsTbl.AsNoTracking() .Join(_dbContext.AddressTbl.AsNoTracking(), client => client.Id, address => address.ClientId, (client, address) => new Clients { Id = client.Id, Name = client.Name, Address = address.FullAddress }); }
这个返回的IQueryable只是查询计划,不会立即执行数据库查询。只有当调用方执行ToListAsync()、FirstAsync()等方法时,才会真正去数据库取数据。
如果业务需要立即执行并返回List,保留原接口的Task<List<Clients>>,但要去掉多余的AsQueryable(),并把Clients的映射逻辑整合到查询中:
public async Task<List<Clients>> ListAllClients() { return await _dbContext.ClientsTbl.AsNoTracking() .Join(_dbContext.AddressTbl.AsNoTracking(), client => client.Id, address => address.ClientId, (client, address) => new Clients { Id = client.Id, Name = client.Name, Address = address.FullAddress }) .ToListAsync(); }
这样查询会在数据库端完成关联和映射,效率更高。
问题3:填充Clients列表的代码重构
你原来的var allClients = query.Select(...)完全可以整合到数据库查询中,不需要先查匿名类型再在内存中转换——这种做法会把所有匿名类型数据加载到内存后再转成Clients,浪费内存和性能。
直接在Join的结果中构造Clients对象(如上面的代码所示),EF Core会把映射逻辑转换成SQL,直接从数据库返回符合Clients结构的数据,不需要内存中二次处理。
另外,原代码中return _clientsList是严重bug,必须修正为返回查询结果。
最终重构后的完整代码(两种场景)
场景1:返回延迟执行的IQueryable
public class Clients { public int Id { get; set; } // 补充类型定义 public string Name { get; set; } public string Address { get; set; } } public interface IClientsRepository { IQueryable<Clients> ListAllClients(); } public class ClientsRepository : IClientsRepository { private readonly MainDbContext _dbContext; public ClientsRepository(MainDbContext dbContext) { _dbContext = dbContext; } public IQueryable<Clients> ListAllClients() { return _dbContext.ClientsTbl.AsNoTracking() .Join(_dbContext.AddressTbl.AsNoTracking(), client => client.Id, address => address.ClientId, (client, address) => new Clients { Id = client.Id, Name = client.Name, Address = address.FullAddress }); } }
场景2:立即执行并返回List
public class Clients { public int Id { get; set; } // 补充类型定义 public string Name { get; set; } public string Address { get; set; } } public interface IClientsRepository { Task<List<Clients>> ListAllClients(); } public class ClientsRepository : IClientsRepository { private readonly MainDbContext _dbContext; public ClientsRepository(MainDbContext dbContext) : base(dbContext) { _dbContext = dbContext; } public async Task<List<Clients>> ListAllClients() { return await _dbContext.ClientsTbl.AsNoTracking() .Join(_dbContext.AddressTbl.AsNoTracking(), client => client.Id, address => address.ClientId, (client, address) => new Clients { Id = client.Id, Name = client.Name, Address = address.FullAddress }) .ToListAsync(); } }
内容的提问来源于stack exchange,提问作者mustafa00

