如何使用DocumentDb、LINQ和F#返回多个属性?

问题描述 投票:0回答:2

运行下面代码的 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# azure-cosmosdb c#-to-f#
2个回答
4
投票

错误显示“不支持构造函数调用”。这是一个合理的限制,大多数 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
的子类,所以你的演员表会可能会失败。


0
投票

(最终将完善并重新发布;这可能会出现在 Equinox.CosmosStore 中)


我目前不求助于 C# 的最佳技巧是非常仔细地将项目投影为

obj[]

  1. 执行查询,得到

    IQueryable<obj[]>
    :

    let all: IQueryable<obj[]> =
        ...
       .Select(fun i -> [| i._etag; i.u[0] |] : obj[])
    

    (在我的例子中,这是一个

    string
    和一个 JSON 对象)

  2. ToQueryDefinition
    为您提供查询

  3. 确保为

    CosmosClient

    提供 STJ 串行器
    let ser = Equinox.CosmosStore.Core.CosmosJsonSerializer(System.Text.Json.JsonSerializerOptions())
    let client = new CosmosClient(cs, clientOptions = CosmosClientOptions(Serializer = ser))
    
  4. 使用

    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"
    
  5. 使用类似的方法遍历结果:

    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}")
© www.soinside.com 2019 - 2024. All rights reserved.