OData – Query to Expression

前言

EF Core 可以把 expression 转换成 string, 但没办法转回来. 

想把 string 转成 expression, 目前最合适的工具是 OData. 虽然 Dynamic LINQ 也有人用, 但毕竟 OData 是微软的, 而且有规范文档.

可惜, 就目前的 OData 要想做到 string to expression 还是很麻烦的. 这也是 OData.NxT 想达到的其中一个目标. 但以 OData Team 的实力, 估计还要等好多年呢. 我们还是实际一点, 来看看目前该怎么弄吧.

 

主要参考:

OData Nxt 004: Query to Expression (IQueryable)

Using Uri Parser

 

分析源码之路

如果你只是想看结果, 可以跳过这个 part, 如果想学习怎样通过源码找方案可以看看.

OData 肯定是有办法做到的, 在 ODataController 里, 可以通过 ODataQueryOptions.ApplyTo 来把 query params apply 进去 queryable.

猜测它内部就是把 string 转成 expression 然后调用 queryable.where(expression), 大概是这样.

但我们要怎样从 0 搞起呢? 怎么弄 OdataQueryOptions 出来呢?

接着查下去.

打开 ODataQueryOptions.cs

发现要初始化它就需要有一个 HttpRequest...这个接口太上层了吧, 和 HttpRequest 绑的这么死...

接着查下去

在 ApplyTo 方法里看到了 handle $filter 的 apply. 我这里只 focus $filter 的处理 (因为我这一次也只需要到这个).

底层调用的是 FilterQueryOption.ApplyTo

FilterQueryOption.cs

初始化它需要 content 和 parser. 嗯...感觉这个接口就比较底层了, 是我们要的了. 先不管 context, parser 怎么搞, 我们去看看它的 apply 做些什么.

ApplyTo

和我们猜测的一样, 里面搞了一个 queryable.where(expression). 它是利用了 FilterBinder.Bind 方法来把 string 转成 expression.

如果你有看上面的参考视频, 这个就是作者通过反射调用的方法. 因为这个 Bind 是 internal 方法来的, 所以他要反射.

FilterBinder.cs

好, 既然确定找对了路, 那么我们回去看看怎样从 0 做出来, context 和 parser 怎样弄.

ODataQueryContext.cs

初始化需要 EdmModel, ClrType, ODataPath.

EdmModel 就是我们每次 startup.cs 要搞的, 它和 EF Core 的 model 是同一个概念.

var odataBuilder = new ODataConventionModelBuilder();
odataBuilder.EntitySet<Product>("products");
odataBuilder.EnableLowerCamelCase();
var edmModel = odataBuilder.GetEdmModel();

ClrType 指的就是这一次的 $filter 的起头. OData 是对着 resource (entity) 的, 一定会有一个开始的 entity.

上面例子就是 typeof(Product).

ODataPath 我们参考它怎么做.

EnableQueryAttribute.cs

在 request 阶段就已经生产的, 全场找一下后它是这样做的

var entitySet = edmModel.FindDeclaredEntitySet("products");
var entitySetSegment = new EntitySetSegment(entitySet);
var odataPath = new ODataPath(new[] { entitySetSegment });

context 搞定, 下一个要做的是 parser.

ODataQueryOptionParser.cs (源码是在 odata.net library)

初始化有几个重载, 先挑一个最简单实现的呗.

model 有了, odataPath 有了,

queryOptions

Using Uri Parser 学的, 但这里只教到怎样 parser 出 filter clause 但是没有教怎样转成 expression.

搞定. 到这里我们就掌握了所有的资料了.

 

写 Demo

var odataBuilder = new ODataConventionModelBuilder();
odataBuilder.EntitySet<Product>("products");
odataBuilder.EnableLowerCamelCase();
var edmModel = odataBuilder.GetEdmModel(); // 做 edm model
var entitySet = edmModel.FindDeclaredEntitySet("products");
var entitySetSegment = new EntitySetSegment(entitySet);
var odataPath = new ODataPath(new[] { entitySetSegment }); // 做 odata path
var queryOptions = new Dictionary<string, string> // 定义 $filter query
{
    ["$filter"] = "id eq 2",
};
var queryOptionParser = new ODataQueryOptionParser(edmModel, odataPath, queryOptions); // 做 parser
var context = new ODataQueryContext(edmModel, typeof(Product), odataPath); // 做 context
var filterQueryOption = new FilterQueryOption($"id eq 2", context, queryOptionParser); // 做 filter options
var productQuery = new List<Product> { new Product { Id = 1 }, new Product { Id = 2 } }.AsQueryable(); // 定义 queryable
var newQuery = filterQueryOption.ApplyTo(productQuery, new ODataQuerySettings()); // apply to
var result = newQuery.OfType<Product>().ToList(); // 等到结果

这样就实现了一个通过 OData 把 query string apply to queryable 的过程了.

那如果我不想 apply 只想获取到 expression 行不行? 

不行, 除非像参考视频里那样, 通过反射去调用 internal 的 Bind 方法. 然后做 apply to 所有的动作, 除了最后一个 where.

 

总结

OData 确实很不错, 但想把 query string 转换成 expression 目前还没有一个 build-in 的 solution. 

也有一些 library 可以做到类似的效果

Is there a ODATA query to linq where expression (ODATA to Linq )

本篇尝试探索如果利用 OData 现有的接口来尽量做到 query to expression. 同时期待 OData.NxT 带给开发者们更好的使用体验.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章