免责声明:由于我们都熟悉它,我将使用contoso university设计来解释我的问题。此外,我在mvc代码优先设计上使用EF核心和.net核心2.0。
我正在开发一个适用于任何模型的非常通用的RESTful API。它只在一个控制器中为每个创建,读取,更新和删除操作提供一种方法,其路由是
[Route("/api/{resource}")]
资源是客户希望使用的实体,例如,如果有人希望使用api获取所有课程,则必须在http://www.example.com/api/course/或{{{{}}上执行GET请求3}}通过id获取一个,以下代码将完成这项工作。
[HttpGet("{id:int:min(1)?}")]
public IActionResult Read([FromRoute] string resource, [FromRoute] int? id)
{
//find resourse in models
IEntityType entityType = _context.Model
.GetEntityTypes()
.FirstOrDefault(x => x.Name.EndsWith($".{resource}", StringComparison.OrdinalIgnoreCase));
if (entityType == null) return NotFound(resource);
Type type = entityType.ClrType;
if (id == null)//select all from table
{
var entityRows = context.GetType().GetMethod("Set").MakeGenericMethod(type).Invoke(context, null);
if (entityRows == null)
return NoContent();
//TODO: load references (1)
return Ok(entityRows);
}
else //select by id
{
var entityRow = _context.Find(type, id);
if (entityRow == null)
return NoContent();
//TODO: load references (2)
return Ok(entityRows);
}
}
这一小段代码将通过一个小异常来完成魔术,不会加载中间集合。根据我们的示例,所提取的课程将没有 CourseInstructor (课程和人员之间的中间集合)的信息。我试图找到一种方法,只有当它是一个集合时,才能加载导航属性;或者通过任何其他条件确保只加载多对多关系。
对于// TODO:加载引用(2)我可以使用
_context.Entry(entityRow).Collection("CourseInsructor").Load();
在运行时,如果我能找到所有导航属性(按语音条件过滤)和foreach,我做了Load(),我应该得到所需的结果。我的问题是,当我得到所有(当id为null)时,entityRows是类型' InternalDbSet'这是一个未知的模型。
因此对于两个TODO,我需要一些帮助才能执行以下步骤
1:仅查找多对多关系的导航属性 2:加载它们
有什么建议吗?
答案 0 :(得分:1)
总的来说,这对我来说似乎是一个非常糟糕的主意。虽然CRUD的内容对于大多数资源来说都是相同的,但会有差异(正如您现在遇到的那样)。有一个自我记录的API还有一些东西可以说:通过单独的控制器,您可以知道哪些资源可以通过控制器与该资源相关联来访问。用他们的方式你做它,它是一个完整的黑盒子。这当然也会影响任何类型的实际生成的API文档。例如,如果您在项目中包含Swagger,则无法确定您在此处执行的操作。最后,您现在必须使用所有的反射,这将影响您的表现。
我建议改为创建一个基本抽象控制器,然后为从中继承的每个唯一资源创建一个控制器,例如:
TEntity
我只是想给出一个简单的例子,但你会以同样的方式构建所有其余的CRUD方法,一般使用public class WidgetController : BaseController<Widget>
{
public WidgetController(MyContext context)
: base(context)
{
}
}
。然后,对于每个实际资源,您只需执行以下操作:
public virtual IQueryable<TEntity> GetQueryable()
=> _context.Set<TEntity>();
没有重复的代码,但您现在有一个真正的真实控制器支持资源,帮助您的API的固有和可能明确的文档。而且,任何地方都没有反映。
然后,为了解决你在这里遇到的问题,你可以为你的基本控制器添加钩子:基本上只是你的基本控制器的CRUD动作中使用的虚拟方法,什么都不做或只是默认的事情。但是,您可以在派生控制器中将这些覆盖为存根以增加其他功能。例如,您可以添加以下内容:
public class CourseController : BaseController<Course>
{
...
public override IQueryable<Course> GetQueryable()
=> base.GetQueryable().Include(x => x.CourseInstructors).ThenInclude(x => x.Instructor);
然后,在派生控制器中,您可以执行以下操作:
BaseController.Index
因此,举例来说,您可以使用GetQueryable()
操作,利用metavar
获取要显示的实体列表。只需在派生类上覆盖它,就可以根据特定类型资源的上下文改变发生的事情。