运行下面代码的 F# 版本时出现错误,而下面的 C# 版本可以运行。关于如何使用 F# 从 linq 查询返回多个 documentdb 属性有什么想法吗?
2016-12-29T23:57:08.504 Exception while executing function: Functions.GetTags. mscorlib: Exception has been thrown by the target of an invocation. mscorlib: One or more errors occurred. Microsoft.Azure.Documents.Client: Constructor invocation is not supported.
C#
var userTagsQuery =
userDocument.Where(user => user.topics != null && user.education != null)
.Select(user => new {topics=user.topics, education=user.education});
F#
type UserTagRecord = {topics : string list; education : string list}
let userTagsQuery =
user.Where(fun user -> user.topics <> null && user.education <> null)
.Select(fun user -> {topics=user.topics :?> string list; education=user.education :?> string list})
错误显示“不支持构造函数调用”。这是一个合理的限制,大多数 LINQ 提供商都有它。
在 F# 代码中,构造函数调用就是记录创建。 F# 记录被编译为具有一堆只读属性的类和一个将这些属性的值作为参数的构造函数。根据错误信息,不支持调用该构造函数。运气不好。
值得注意的一件有趣的事情是,C# 匿名类型(在 C# 代码中使用)的工作方式与 F# 记录完全相同 - 它们是具有一堆只读属性和单个构造函数的类, - 然而,它们是受支持的。将 C# 匿名类型作为特殊情况处理也是 LINQ 提供程序的常见做法。许多 LINQ 提供程序会以更通用的方式处理它,也涵盖 F# 记录,但在本例中,情况显然并非如此。如果我是你,我会就此提出一个问题。
您可以尝试将记录替换为具有可变属性的类,并使用属性初始化语法构造它:
type UserTagRecord() =
member val topics : string list = [] with get, set
member val education : string list = [] with get, set
let userTagsQuery =
user
.Where(fun user -> user.topics <> null && user.education <> null)
.Select(fun user -> UserTagRecord( topics=user.topics :?> string list, education=user.education :?> string list ) )
我还想冒险建议您在使用 F# 列表时可能会遇到更多麻烦。首先,DocumentDb 本身可能不喜欢它们,其次,我不知道
user.topics
和 user.education
是什么,但我很确定它们不是 string list
的子类,所以你的演员表会可能会失败。
(最终将完善并重新发布;这可能会出现在 Equinox.CosmosStore 中)
我目前不求助于 C# 的最佳技巧是非常仔细地将项目投影为
obj[]
执行查询,得到
IQueryable<obj[]>
:
let all: IQueryable<obj[]> =
...
.Select(fun i -> [| i._etag; i.u[0] |] : obj[])
(在我的例子中,这是一个
string
和一个 JSON 对象)
让
ToQueryDefinition
为您提供查询
确保为
CosmosClient
提供 STJ 串行器
let ser = Equinox.CosmosStore.Core.CosmosJsonSerializer(System.Text.Json.JsonSerializerOptions())
let client = new CosmosClient(cs, clientOptions = CosmosClientOptions(Serializer = ser))
使用
JsonIsomorphism
解析它:
[<StjConverter(typeof<StjResultParser>)>]
type Result = { etag: string; unfold: Unfold<string> }
and StjResultParser() =
inherit FsCodec.SystemTextJson.JsonIsomorphism<Result, System.Text.Json.JsonElement[]>()
let serdes = FsCodec.SystemTextJson.Serdes.Default
override _.UnPickle input = { etag = serdes.Deserialize input[0]; unfold = serdes.Deserialize input[1] }
override _.Pickle value = invalidOp "readOnly"
使用类似的方法遍历结果:
let private taskEnum (iterator: FeedIterator<'T>) = taskSeq {
while iterator.HasMoreResults do
let! response = iterator.ReadNextAsync()
yield response.Diagnostics.GetClientElapsedTime(), response.RequestCharge, response.Resource }
let fetch<'T> (desc: string) (container: Container) (queryDefinition: QueryDefinition) = taskSeq {
if Log.IsEnabled Serilog.Events.LogEventLevel.Debug then Log.Debug("CosmosQuery.fetch {desc} {query}", desc, queryDefinition.QueryText)
let sw = System.Diagnostics.Stopwatch.StartNew()
let iterator = container.GetItemQueryIterator<'T>(queryDefinition)
let mutable responses, items, totalRtt, totalRu = 0, 0, TimeSpan.Zero, 0.
try for rtt, rc, response in taskEnum iterator do
responses <- responses + 1
totalRu <- totalRu + rc
totalRtt <- totalRtt + rtt
for item in response do
items <- items + 1
yield item
finally Log.Information("CosmosQuery.fetch {desc} found {count} ({trips}r, {totalRtt:N0}ms) {rc}RU {latency}ms",
desc, items, responses, totalRtt.TotalMilliseconds, totalRu, sw.ElapsedMilliseconds) }
最后你可以像这样查看结果:
all.ToQueryDefinition()
|> CosmosStoreQuery.fetch<Result> "items" source.Container
|> TaskSeq.iter (fun x -> printfn $"%s{x.etag} %O{x.unfold}")