G L*_*ley 2 c# rest entity-framework repository-pattern odata
我再次审查 OData,因为我想在 EF 的新 Rest 项目中使用它,但我也有与几年前相同的担忧。
公开通用的 IQueryable 可能非常危险。限制潜在昂贵的查询必须在其他地方完成。DB,连接级别。
OData 不允许开发人员对行为进行任何拦截/自定义,因为它位于界面之外。
一般来说,OData 与 DI 配合得不好。虽然可以 DI 替代 IQueryable,但您无法拦截 OD 调用并检查、修改或修改。
我的建议是将该工具分解为更多不同的元素,以允许更大的定制和重用。打破黑匣子:)在单一责任方面也会更好。是否有可能有执行以下操作的组件
来自 url 的表达式生成器。将 OData url 扩展转换为可与 IQueryable 一起使用但独立于 IQueryable 的类型化表达式。例如,为 where 生成 T => Expression<Func<T, bool>> 。这将是一个超级有用的独立组件,并支持作为标准更广泛使用的 OData url 格式。
用于将表达式附加到 EF 上下文的 EF 适配器。一个 EF 适配器,用于将表达式附加到 EF 上下文或在任何其他 DI 代码中使用。因此,服务可以封装接口并获得 OData 功能的优势,而不是公开公共 IQueryable。其余获取 -> 表达式生成 -> 映射到 IQueryable。
这种方法将允许开发人员拦截查询调用并根据需要自定义行为,同时保持简单情况的易用性。我们可以将 OData 和 EF 嵌入到存储库模式中,在其中添加我们自己的功能。
您的帖子中有很多误解,它不太适合这个网站,但这是一个反复出现的猜测,确实需要解决。
- 一般来说,OData 与 DI 配合得不好。虽然可以 DI 替代 IQueryable,但您无法拦截 OD 调用并检查、修改或修改。
这种说法根本不准确,既不涉及 DI 主题,也不涉及查询拦截。详细说明超出了范围,因为有许多不同的方法可以实现这一目标,最好发布您遇到挑战的特定场景,我们可以发布特定的解决方案。
- 公开通用的 IQueryable 可能非常危险。限制潜在昂贵的查询必须在其他地方完成。DB,连接级别。
如果不施加任何限制,将 rawIQueryable作为一个概念IQueryable公开会存在一些固有的危险,但在 OData 中,我们根本不会向公众公开,而不是传统的 SDK 或直接 API 意义上的公开。是的,您的控制器方法可以(并且应该)返回一个,IQueryable但 OData 会解析传入 Http 请求中的路径和查询,以组成最终查询来服务请求,而无需将数据预先加载到内存中。
IQueryable于当您允许外部逻辑编写或执行附加到内部数据上下文的查询时,但在 OData 中,HTTP 主机边界阻止外部操作员直接与您的查询或代码交互,因此这种风险并不存在。由于托管模型而存在。OData 为您提供了哪些字段可用于投影、过滤或排序的粒度,尽管对编写扩展查询(包括函数和聚合)提供了丰富的支持,但表达式IQueryable本身并没有超越可执行接口的边界。方法IQueryable响应本身是促使我们首先选择 OData 的许多功能的基础。
IQueryable不过,如果你实在不愿意的话,根本不需要暴露!您可以IEnumerable改为返回,但如果您想满足查询请求,则需要将足够的数据加载到内存中。有一些扩展点可以帮助您完成此操作,也有一些工具可以将 URL 查询参数解析为简单的字符串或表达式树,您可以根据需要将其应用到您自己的数据模型中。
这EnableQueryAttribute是一个操作过滤器,它将对来自控制器端点的结果组成LINQ 查询,以应用任何$filter条件或$select/$expand投影,甚至$apply聚合。
- OData 不允许开发人员对行为进行任何拦截/自定义,因为它位于界面之外。
EnableQueryAttribute与您在 OData 中可以找到的黑匣子差不多,但 OData 库是完全开源的,您可以扩展或覆盖实现或完全省略该属性。如果您这样做(忽略它),您将需要处理响应并将其格式化为符合 OData 要求。该规范具有高度的灵活性,主要的警告是您需要确保$metadata文档描述了输入和输出。
如果您的端点不返回,则 中的IQueryableLINQ 组合EnableQueryAttribute只能对提要中的数据进行操作IEnumerable。其含义的一个简单示例是,如果 URL 查询包含$select单个字段的参数,如下所示:
http://my.service.net/api/products(101)?$select=Description
Run Code Online (Sandbox Code Playgroud)
如果您仅公开IEnumerable,则必须手动从底层存储加载数据。您可以使用该类ODataQueryOptions通过结构化接口访问 OData 参数,具体语法当然会根据您的 DAL、ORM 和实际模型而有所不同。然而,像大多数 Repository 或 MVC 实现一样,许多不使用的实现IQueryable将默认简单地将整个对象加载到内存中,而不是专门请求的字段,它们最终可能会加载此比较 SQL 查询的结果:
SELECT * FROM Product WHERE Id = @Id
Run Code Online (Sandbox Code Playgroud)
如果该产品有 20 个字段,则所有数据都将具体化到内存中以服务该请求,即使只请求了 1 个字段。即使不使用IQueryable,OData 仍然具有显着的优势,因为它可以减少通过线路发送到客户端应用程序的字节数。这不仅降低了成本,还缩短了满足请求所需的时间。
相比之下,如果控制器方法返回一个IQueryable已延迟或尚未实现的表达式,那么最终执行的 SQL 可能会更加具体:
SELECT Description FROM Product WHERE Id = @Id
Run Code Online (Sandbox Code Playgroud)
这可以带来显着的性能优势,不仅在 SQL 执行方面,而且在数据存储和服务层之间的传输以及接收到的数据的序列化方面。
要充分实现性能提升,需要客户端选择性地进行数据调用。如果最终客户端进行调用以显式请求所有字段,那么 OData 和传统 API 方法之间应该没有区别,但使用 OData 就可以实现潜力。
如果控制器公开一个复杂的视图,而不是传统的表,那么支持IQueryable. 对于与底层存储模型不匹配的自定义业务 DTO(视图),我们常常被迫在性能实用性和数据结构之间做出妥协。如果没有允许调用者修剪数据架构的 OData,API 常常会实现一些完全动态的端点,或者会看到大量范围有限或可能具有单一用途的类似 DTO 模型。OData 提供了一种机制来公开单个公共视图,该视图具有比所有调用者所需的更多元数据,同时仍然允许单个调用者仅检索他们需要的子集。
在聚合视图中,您最终可能会得到一些单独的列,对整体查询执行产生重大影响,在传统的 REST API 中,这成为拥有类似 DTO 模型的常见理由,使用 OData,我们可以定义一次视图,并为调用者提供灵活的选择何时应该查询需要较长响应等待时间的额外数据,何时不应该查询。
OData 提供的灵活性可以减少前端开发团队开始使用您的服务时经常出现的视图和复杂类型的迭代演进,从而显着缩短总体上市时间。OData 标准的性质IQueryable和提供的约定意味着前端工作有可能在 API 完全实现之前开始
这是一个非常简单且人为的示例,我们尚未涉及$expand或$apply可能导致需要支持的内存密集型操作。然而,我将很快讨论一下$count,这是一个看似简单的要求,返回特定条件或根本没有条件的所有记录的计数。ODataIQueryable实现不需要额外的代码,并且几乎需要零处理来服务此请求,因为它可以以以下形式完全传递到底层数据存储:SELECT COUNT(*) FROM...
IQueryable反对从 DbContext 公开的一个关键论点IQueryable是,它可能允许调用者访问比您预期更多的数据库内容。OData 针对此问题提供了多种保护措施。第一个是,对于整个模式中的每个字段,您可以指定该字段是否可用、是否可以过滤或可以排序。
下一个级别的保护是,对于每个端点,我们可以指定整体扩展深度,默认情况下为 2。
值得一提的是,没有必要直接通过 OData 公开您的数据模型,如果您的领域模型与您的数据模型不一致,则仅通过 OData API 公开选定的视图或 DTO 可能是可行的,或者仅公开架构中的表的子集。
来自 url 的表达式生成器。将 OData url 扩展转换为可与 IQueryable 一起使用但独立于 IQueryable 的类型化表达式。例如,为 where 生成 T => Expression<Func<T, bool>> 。
这是一个有问题的概念,如果您不开放IQueryable......话虽这么说,您可以使用开放类型并且可以拥有一个完全动态的模式,您可以实时验证该模式或完全从查询路由中派生而无需验证。关于此的已发布文档并不多,主要是因为您想要实现的场景非常具体,但整理起来并不难。虽然超出了本文的范围,但如果您向 SO 提出问题并考虑到特定场景,我们可以发布具体的实施建议......
用于将表达式附加到 EF 上下文的 EF 适配器。一个 EF 适配器,用于将表达式附加到 EF 上下文或在任何其他 DI 代码中使用。因此,服务可以封装接口并获得 OData 功能的优势,而不是公开公共 IQueryable。其余获取 -> 表达式生成 -> 映射到 IQueryable。
您所描述的内容与 OData 上下文的工作方式非常接近。要配置 OData,您需要指定OData 模型公开的实体的结构。OOTB 提供了基于约定的映射器,可以帮助您使用最少的代码公开接近 1:1 实体框架 DbContext 模型表示的 OData 模型,但 OData 根本不依赖于 EF。唯一的要求是您定义 DTO 模型,包括操作和函数,从此模型中,OData 运行时能够验证传入的 HTTP 请求并将其解析为由控制器提供的基本表达式组成的可查询表达式。
我不推荐这样做,但我见过很多使用 AutoMapper 在 EF 模型和 DTO 之间进行映射,然后将 DTO 映射到 OData 实体模型的实现。OData 模型本身就是一个 ORM,它在您的内部模型和您想要通过 API 公开的模型之间进行映射。如果这个模型是一个显着不同的结构或涉及不同的关系,那么 AutoMapper 是合理的。
ODataController如果您不愿意,则不必实现整个 OData 运行时,包括 OData 实体模型配置和继承。
当您想要在 ASP.NET Web API 2 中支持 OData 查询选项而不完全实现 OData API 时,通常的方法是在您的标准 API 中使用EnableQueryAttribute,它毕竟只是一个操作筛选器...以及如何使用 OData API 的示例OData 库已以某种方式打包,您可以在其他 API 模式中实现 OData 查询约定。