EF Core TPT继承模型下如何通过LINQ选择对应DTO?
问题描述
我有以下实体类:
public class User{ public Guid Id{get; set;} public string Name {get; set;} } public class TestUser : User{ public string Title {get; set;} } public class CustomUser: User{ public string Value {get; set;} }
实体类采用TPT继承模式(每个类对应独立数据库表),EF配置如下:
modelBuilder.Entity<TestUser>().HasBaseType<User>(); modelBuilder.Entity<CustomUser>().HasBaseType<User>();
同时每个实体类都有对应的DTO类,同样采用继承结构:
public class UserGetDto{} public class TestUserGetDto : UserGetDto{} public class CustomUserGetDto : UserGetDto{}
我尝试用LINQ结合switch表达式查询并返回对应DTO,但无法生效:
public List<UserGetDto> GetUsers(){ return _context.Users .Select(user => user switch{ User usr when usr is TestUser => new TestUserGetDto(){ ... }, User usr when usr is CustomUser => new CustomUserGetDto(){ ... }, _ => new UserGetDto(){...} }) .ToList(); }
请问有没有其他方法可以正确映射到对应的DTO?
已尝试的方案(更新)
参考建议使用AutoMapper后,找到了一个可行方案:
添加UserProfile并做如下配置:
CreateMap<User, UserGet>() .ConvertUsing(user => user is TestUser ? new TestUserGet(){...} : user is CustomUser ? new CustomUserGet(){...} : new UserGet() );
初步测试符合预期,但缺点是需要手动配置TestUserDto等子类的映射逻辑。
可选解决方案
1. 内存中处理类型映射
EF Core对TPT继承的LINQ类型转换支持有限,可以先将数据加载到内存,再做类型判断和DTO映射:
public List<UserGetDto> GetUsers(){ var users = _context.Users.ToList(); // 先加载到内存 return users.Select(user => { return user switch{ TestUser testUser => new TestUserGetDto(){ Id = testUser.Id, Name = testUser.Name, Title = testUser.Title }, CustomUser customUser => new CustomUserGetDto(){ Id = customUser.Id, Name = customUser.Name, Value = customUser.Value }, _ => new UserGetDto(){ Id = user.Id, Name = user.Name } }; }).ToList(); }
这种方式直接简单,但数据量较大时全量加载会影响性能。
2. AutoMapper优化继承映射配置
针对你已用的AutoMapper,可以优化配置,复用基类映射规则,避免全手动写转换逻辑:
public class UserProfile : Profile { public UserProfile() { // 配置基类属性映射 CreateMap<User, UserGetDto>() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)); // 子类映射自动继承基类规则,只需配置子类独有的属性 CreateMap<TestUser, TestUserGetDto>() .IncludeBase<User, UserGetDto>() .ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title)); CreateMap<CustomUser, CustomUserGetDto>() .IncludeBase<User, UserGetDto>() .ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Value)); } }
使用时通过ProjectTo直接在EF查询中转换,无需全量加载内存:
public List<UserGetDto> GetUsers(){ return _context.Users .ProjectTo<UserGetDto>(_mapper.ConfigurationProvider) .ToList(); }
AutoMapper会自动识别实体类型并映射到对应DTO子类,同时支持EF查询翻译,性能更优。
3. 编写映射扩展方法
为每个实体类封装映射扩展方法,逻辑清晰易维护:
public static class UserMappingExtensions { public static UserGetDto ToDto(this User user) { return user switch{ TestUser tu => tu.ToDto(), CustomUser cu => cu.ToDto(), _ => new UserGetDto(){ Id = user.Id, Name = user.Name } }; } public static TestUserGetDto ToDto(this TestUser testUser) { return new TestUserGetDto(){ Id = testUser.Id, Name = testUser.Name, Title = testUser.Title }; } public static CustomUserGetDto ToDto(this CustomUser customUser) { return new CustomUserGetDto(){ Id = customUser.Id, Name = customUser.Name, Value = customUser.Value }; } }
在LINQ中调用即可:
public List<UserGetDto> GetUsers(){ return _context.Users.ToList().Select(u => u.ToDto()).ToList(); }
内容的提问来源于stack exchange,提问作者SNO
相关产品推荐
相关产品推荐

