C#如何从DbContext动态选择DbSet实现泛型OData控制器
问题修复方案
错误原因说明
你遇到的问题根因如下:
- CS0452编译错误:
DbSet<TEntity>对泛型参数有引用类型约束,你定义泛型控制器时未给类型参数T添加对应约束,编译器无法确认T是合法的DbSet实体类型。 - 反射逻辑错误:你在调用属性getter时传入的实例是当前控制器对象
this,但DbSet属性是定义在DS2DbContext上的,应该传入_context实例;同时按nameof(T)匹配属性的逻辑鲁棒性差,容易出现属性名和类型名不匹配的问题。 - 隐藏逻辑错误:按ID查询的方法返回值写死为
SingleResult<WatermarkFileData>,不支持泛型;且直接访问c.ID时编译器无法确认类型T包含ID属性,会产生新的编译错误。
最优实现方案
不需要手写反射逻辑,EF Core内置的DbContext.Set<T>()方法可以直接获取实体类型对应的DbSet,配合泛型约束即可实现你要的通用控制器能力。
- 首先定义公共实体接口,统一两类同结构实体的属性约定,避免泛型调用属性时的编译错误:
public interface IFileData { Guid ID { get; set; } // 此处补充两张表共有的其他属性 } // 两类实体实现该接口 public class DocumentFileData : IFileData { public Guid ID { get; set; } // 其余自有属性 } public class WatermarkFileData : IFileData { public Guid ID { get; set; } // 其余自有属性 }
- 修正泛型基类控制器,添加正确的泛型约束,使用EF Core内置方法获取DbSet:
public class FileDataController<T> : ODataController where T : class, IFileData { private readonly DS2DbContext _context; private readonly ILogger<FileDataController<T>> _logger; public FileDataController(DS2DbContext dbContext, ILogger<FileDataController<T>> logger) { _logger = logger; _context = dbContext; } private DbSet<T> DB() => _context.Set<T>(); [EnableQuery(PageSize = 15)] public IQueryable<T> Get() { return DB(); } [EnableQuery] public SingleResult<T> Get([FromODataUri] Guid ID) { var result = DB().Where(c => c.ID == ID); return SingleResult.Create(result); } }
- 具体业务控制器只需要继承泛型基类,传入对应实体类型即可,不需要重复编写查询逻辑:
public class DocumentFileDataController : FileDataController<DocumentFileData> { public DocumentFileDataController(DS2DbContext dbContext, ILogger<FileDataController<DocumentFileData>> logger) : base(dbContext, logger) { } } public class WatermarkFileDataController : FileDataController<WatermarkFileData> { public WatermarkFileDataController(DS2DbContext dbContext, ILogger<FileDataController<WatermarkFileData>> logger) : base(dbContext, logger) { } }
反射版本实现(特殊场景可选)
如果你有特殊需求必须通过反射匹配DbContext属性获取DbSet,可以参考以下修正后的代码,避免原反射逻辑的错误:
注意:反射版本性能比直接使用
Set<T>()差,且更容易出现运行时错误,非必要不使用。
public DbSet<T> DB() where T : class { // 按属性类型匹配比按名称匹配更可靠,避免属性名和类型名不一致导致的空引用 var targetProperty = _context.GetType() .GetProperties() .FirstOrDefault(prop => prop.PropertyType == typeof(DbSet<T>)); if (targetProperty == null) throw new InvalidOperationException($"当前DbContext中未注册实体类型{typeof(T).FullName}对应的DbSet"); // 注意传入的实例是_context,不是当前控制器 return (DbSet<T>)targetProperty.GetValue(_context); }
内容的提问来源于stack exchange,提问作者Razzupaltuff
相关产品推荐
相关产品推荐

