基于嵌套列表的C# LINQ高效筛选及AutoMapper映射问题
嘿,我来给你拆解这个需求的具体实现方案,分步骤来很清晰:
1. 数据筛选逻辑实现
首先咱们需要从数据库中筛选出包含指定语言翻译的Category,并且只保留该语言的翻译项。用EF Core的LINQ查询来实现最方便:
// 假设你有DbContext,里面包含Categories、CategoryTranslations、Languages三个DbSet public async Task<List<Category>> GetCategoriesWithSpecifiedLanguageAsync(string languageCode) { return await _dbContext.Categories // 关联翻译表和语言表 .Include(c => c.CategoryTranslations) .ThenInclude(ct => ct.Language) // 筛选出存在指定语言翻译的分类 .Where(c => c.CategoryTranslations.Any(ct => ct.Language.Code == languageCode)) // 投影只保留目标语言的翻译,避免加载多余数据 .Select(c => new Category { Id = c.Id, // 映射Category本身的其他属性,比如排序值、状态等 CategoryTranslations = c.CategoryTranslations .Where(ct => ct.Language.Code == languageCode) .ToList() }) .ToListAsync(); }
这里用
Select投影是为了只加载我们需要的翻译数据,比先加载所有翻译再过滤更高效,能减少数据库查询的数据量。
2. AutoMapper配置
接下来配置AutoMapper,把筛选后的Category映射到CategoryOutputModel。先创建一个Profile类:
public class CategoryProfile : Profile { public CategoryProfile() { CreateMap<Category, CategoryOutputModel>() // 映射Category本身的基础属性 .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) // 映射翻译内容——因为筛选阶段已经确保每个分类都有目标语言的翻译,直接取第一个即可 .ForMember(dest => dest.CategoryName, opt => opt.MapFrom(src => src.CategoryTranslations.FirstOrDefault()!.Name)) .ForMember(dest => dest.CategoryDesc, opt => opt.MapFrom(src => src.CategoryTranslations.FirstOrDefault()!.Description)); // 其他需要映射的输出模型属性按需添加 } }
注:如果担心空引用问题,可以加个空判断(比如用
??给默认值),不过因为我们在筛选阶段已经过滤掉了没有目标语言翻译的分类,所以FirstOrDefault()不会返回null,这里用!抑制空警告就好。
3. 完整调用流程
在业务逻辑层,把筛选和映射的逻辑结合起来:
private readonly IMapper _mapper; private readonly ICategoryRepository _categoryRepository; public CategoryService(IMapper mapper, ICategoryRepository categoryRepository) { _mapper = mapper; _categoryRepository = categoryRepository; } public async Task<List<CategoryOutputModel>> GetCategoriesForLanguageAsync(string languageCode) { var filteredCategories = await _categoryRepository.GetCategoriesWithSpecifiedLanguageAsync(languageCode); return _mapper.Map<List<CategoryOutputModel>>(filteredCategories); }
4. 额外优化建议
- 如果
Language表的Code是唯一约束,可以提前查询出LanguageId,用ID来筛选翻译,比字符串匹配更高效:var targetLanguage = await _dbContext.Languages.FirstOrDefaultAsync(l => l.Code == languageCode); if (targetLanguage == null) { // 处理语言不存在的情况,比如返回空列表或抛出异常 return new List<Category>(); } // 后续用targetLanguage.Id来筛选CategoryTranslations - 可以把筛选逻辑封装成可复用的扩展方法,方便在其他地方调用。
- 如果用的是AutoMapper 9+,可以直接用
ProjectTo在数据库查询阶段完成映射,进一步优化性能:
对应的Profile需要调整为支持传递参数:return await _dbContext.Categories .Where(c => c.CategoryTranslations.Any(ct => ct.Language.Code == languageCode)) .ProjectTo<CategoryOutputModel>(_mapper.ConfigurationProvider, new { languageCode }) .ToListAsync();CreateMap<Category, CategoryOutputModel>() .ForMember(dest => dest.CategoryName, opt => opt.MapFrom((src, dest, _, context) => src.CategoryTranslations.First(ct => ct.Language.Code == context.Items["languageCode"] as string).Name));
内容的提问来源于stack exchange,提问作者Matthew Flynn
相关产品推荐
相关产品推荐

