C#如何实现单个API端点获取不同类型的EF泛型列表
解决方案
完全可以实现,以下是两种常用的落地方式:
方案1:单通用端点 + 反射调用泛型方法
这种方式实现成本最低,适合实体数量不多的场景:
- 提前缓存所有实现了
IBaseLookup接口的实体类型映射,避免非法类型传入 - 定义带实体名参数的通用路由,通过反射构造泛型方法调用查询逻辑
代码示例:
// 提前缓存合法的实体类型映射(可以放在控制器构造函数里初始化一次) private readonly Dictionary<string, Type> _lookupEntityTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes()) .Where(t => t.IsClass && !t.IsAbstract && typeof(IBaseLookup).IsAssignableFrom(t)) .ToDictionary(t => t.Name.ToLower(), t => t); // 通用端点 [HttpGet] [Route("lookup/{entityName}")] public async Task<IActionResult> GetLookupList(string entityName, [FromQuery] DataSourceLoadOptions loadOptions) { // 校验实体是否合法 if (!_lookupEntityTypes.TryGetValue(entityName.ToLower(), out var entityType)) { return NotFound($"不存在名为{entityName}的查询实体"); } // 反射构造泛型方法 var method = typeof(ILookupService).GetMethod(nameof(ILookupService.GetLookupListAsync)) ?.MakeGenericMethod(entityType); if (method == null) { return BadRequest("查询方法构造失败"); } // 执行查询 dynamic result = await (dynamic)method.Invoke(_lookupService, null); return Ok(DataSourceLoader.Load(result, loadOptions)); }
调用示例:GET /lookup/title、GET /lookup/test 即可对应原来的两个端点功能。
注意:可以把构造好的泛型方法也缓存到字典里,避免每次反射消耗性能。
方案2:泛型控制器(性能更优,适合实体数量多的场景)
这种方式无需运行时反射,性能和手写端点一致,还支持对单个实体做定制化扩展:
- 定义泛型控制器基类
[Route("api/[controller]")] [ApiController] public class LookupController<T> : ControllerBase where T : class, IBaseLookup { private readonly ILookupService _lookupService; public LookupController(ILookupService lookupService) { _lookupService = lookupService; } [HttpGet] public async Task<IActionResult> GetList([FromQuery] DataSourceLoadOptions loadOptions) { var data = await _lookupService.GetLookupListAsync<T>(); return Ok(DataSourceLoader.Load(data, loadOptions)); } }
- 在程序启动时自动注册所有实体对应的控制器(Program.cs中添加)
builder.Services.AddControllers() .ConfigureApplicationPartManager(manager => { // 扫描所有IBaseLookup实现类,动态生成控制器 var entityTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(a => a.GetTypes()) .Where(t => t.IsClass && !t.IsAbstract && typeof(IBaseLookup).IsAssignableFrom(t)); foreach (var entityType in entityTypes) { var controllerType = typeof(LookupController<>).MakeGenericType(entityType); manager.FeatureProviders.Add(new GenericControllerFeatureProvider(controllerType, entityType.Name)); } });
- 实现
GenericControllerFeatureProvider类来配置控制器名称
public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature> { private readonly Type _controllerType; private readonly string _controllerName; public GenericControllerFeatureProvider(Type controllerType, string controllerName) { _controllerType = controllerType; _controllerName = controllerName; } public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature) { feature.Controllers.Add(_controllerType.GetTypeInfo()); // 配置控制器路由名称,比如Title实体对应的路由就是/api/Title feature.Controllers.Last().ControllerName = _controllerName; } }
调用示例:GET /api/Title、GET /api/Test 即可对应原有端点功能,后续新增实体无需修改任何代码,自动生成对应端点。
内容的提问来源于stack exchange,提问作者Den
相关产品推荐
相关产品推荐

