EF Core加载未知实体的引用

时间:2018-04-03 15:34:15

标签: entity-framework .net-core restful-architecture ef-core-2.0 asp.net-core-mvc-2.0

免责声明:由于我们都熟悉它,我将使用contoso university设计来解释我的问题。此外,我在mvc代码优先设计上使用EF核心和.net核心2.0。

contoso university

我正在开发一个适用于任何模型的非常通用的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:加载它们

有什么建议吗?

1 个答案:

答案 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获取要显示的实体列表。只需在派生类上覆盖它,就可以根据特定类型资源的上下文改变发生的事情。