You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 6中Async/Await结合LINQ的最佳实践咨询

.NET 6多表关联查询的合理性分析与优化建议

1. 当前实现是否合理?

你的代码基础功能是可行的,能完成多表关联查询并返回目标DTO,但存在不少可以优化的细节,算不上最优实现:

  • 手动编写Join语句,代码冗余且可读性差,EF Core本身支持导航属性自动处理关联,没必要手动拼接
  • 方法名存在拼写错误(FetchAllComnpay、GetCompayByIdAsync),影响代码可读性与维护性
  • 空值处理存在硬编码(比如LocalCurrencyId ?? 0),如果业务中该字段是可选的,用0填充可能不符合数据语义
  • 单个查询用FirstAsync,如果未找到匹配ID的企业会直接抛出异常,缺乏容错处理
  • 手动映射字段到DTO,当DTO或实体字段变更时,容易出现遗漏或错误

2. 此类查询可应用的最佳实践

  • 用EF Core导航属性替代手动Join:通过实体类定义关联关系,让EF自动生成高效的关联SQL,代码更简洁易维护
  • 分离映射逻辑:使用AutoMapper的ProjectTo方法自动完成实体到DTO的投影,避免手动编写大量映射代码
  • 合理处理空值:根据业务需求保留null或使用符合语义的默认值,避免硬编码无意义的默认值(如0、空字符串)
  • 完善错误处理:单个查询优先使用FirstOrDefaultAsync,并处理未找到数据的场景(如返回null或抛出自定义业务异常)
  • 遵循命名规范:修正拼写错误,保持方法、变量名的一致性与可读性
  • 按需查询:避免一次性加载所有字段,针对不同业务场景投影到不同的DTO,减少数据传输量

3. 具体优化建议

(1)使用导航属性重构查询

首先在实体类中定义关联导航:

// Company实体
public class Company
{
    public int Id { get; set; }
    public string CompanyName { get; set; }
    // 其他字段...
    public int CityId { get; set; }
    public City City { get; set; } // 导航到City
    public int? LocalCurrencyId { get; set; }
    public Currency LocalCurrency { get; set; } // 导航到本地货币
    public int? InternationalCurrencyId { get; set; }
    public Currency InternationalCurrency { get; set; } // 导航到国际货币
}

// City实体
public class City
{
    public int Id { get; set; }
    public string CityName { get; set; }
    public int CountryId { get; set; }
    public Country Country { get; set; } // 导航到Country
}

然后重构查询方法:

private IQueryable<CompanyDto> FetchAllCompanies()
{
    return from com in _context.Companies
           select new CompanyDto
           {
               Id = com.Id,
               CompanyName = com.CompanyName,
               CompanyCode = com.CompanyCode,
               CountryId = com.City.CountryId,
               CountryName = com.City.Country.CountryName,
               CityId = com.City.Id,
               CityName = com.City.CityName,
               MobileNo = com.MobileNo,
               // 其他字段...
               LocalCurrencyId = com.LocalCurrencyId,
               LocalCurrencyName = com.LocalCurrency?.CurrencyName,
               InternationalCurrencyId = com.InternationalCurrencyId,
               InternationalCurrencyName = com.InternationalCurrency?.CurrencyName
           };
}

(2)引入AutoMapper简化映射

先定义AutoMapper配置:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Company, CompanyDto>()
            .ForMember(dest => dest.CountryId, opt => opt.MapFrom(src => src.City.CountryId))
            .ForMember(dest => dest.CountryName, opt => opt.MapFrom(src => src.City.Country.CountryName))
            .ForMember(dest => dest.CityName, opt => opt.MapFrom(src => src.City.CityName))
            .ForMember(dest => dest.LocalCurrencyName, opt => opt.MapFrom(src => src.LocalCurrency.CurrencyName))
            .ForMember(dest => dest.InternationalCurrencyName, opt => opt.MapFrom(src => src.InternationalCurrency.CurrencyName));
    }
}

然后查询方法可以简化为:

private IQueryable<CompanyDto> FetchAllCompanies()
{
    return _context.Companies.ProjectTo<CompanyDto>(_mapper.ConfigurationProvider);
}

(3)修正方法名与错误处理

public async Task<IEnumerable<CompanyDto>> GetAllCompanies()
{
    return await FetchAllCompanies().AsNoTracking().ToListAsync();
}

public async Task<CompanyDto?> GetCompanyByIdAsync(int id)
{
    // 使用FirstOrDefaultAsync避免未找到时抛出异常
    return await FetchAllCompanies()
        .Where(e => e.Id == id)
        .AsNoTracking()
        .FirstOrDefaultAsync();
}

(4)其他优化点

  • 添加数据库索引:在Company.CityId、City.CountryId、Company.LocalCurrencyId等关联字段上创建索引,提升关联查询的性能
  • 分页支持:如果GetAllCompanies可能返回大量数据,添加分页参数(如page、pageSize),使用Skip()和Take()减少内存占用
  • 避免不必要的默认值:如果LocalCurrencyId是可选字段,DTO中对应字段设为int?,保留null而非硬编码为0,符合数据语义

内容的提问来源于stack exchange,提问作者Mh Dip

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 02:25:17