使用 F# 添加额外的 Id 列在 RavenDb 中保存记录

Saving record in RavenDb with F# adding extra Id column

当我保存一个新的 F# 记录时,我在 RavenDb 文档中得到一个名为 Id@ 的额外列,当我在代码中加载或查看对象时它会显示;它甚至通过我的 F# API.

转换为 JSON

这是我的 F# 记录类型:

type Campaign = { mutable Id : string; name : string; description : string }

我没有做任何令人兴奋的事情来保存它:

let save c : Campaign =
    use session = store.OpenSession()
    session.Store(c)
    session.SaveChanges()
    c

保存记录的新实例会创建 ID 为 campaigns/289 的文档。这是 RavenDb 中文档的完整值:

{
    "Id@": "campaigns/289",
    "name": "Recreating Id bug",
    "description": "Hello Whosebug!"
}

现在,当我在 C# 中使用同一个数据库(和文档)时,我没有获得额外的 Id@ 值。这是我在 C# 中保存记录时的样子:

{
    "Description": "Hello Whosebug!",
    "Name": "Look this worked fine",
}

(另外 - "name" vs "Name" 意味着我的文档中有 2 个名称列。我至少理解这个问题)。

所以我的问题是:如何摆脱在 RavenDb 中保存 F# 记录时创建的额外 Id@ 属性?

这是...的组合...好吧,你不能完全称它们为 "bugs",所以让我们在 F# 编译器和 RavenDb 中都称其为 "non-straightforward features"。

F# 编译器为 Id 记录字段生成一个 public 支持字段。此字段名为 Id@(所有 F# 支持字段的标准模式),并且它是 public,因为记录字段是可变的。对于不可变记录字段,支持字段将为 internal。为什么它需要为可变记录字段生成一个 public 支持字段,我不知道。

现在,RavenDb 在生成模式时,显然会同时查看属性 字段。这有点不标准。通常的做法是只考虑属性。但是,遗憾的是,Raven 选择了名为 Id@ 的 public 字段,并将其作为模式的一部分。

您可以通过两种方式解决这个问题:

首先,您可以使 Id 字段不可变。我不确定这是否适合您或 RavenDb。也许不是,因为 Id 可能是在插入时生成的。

其次,您可以声明您的 Campaign 不是 F# 记录,而是真正的 class:

type Campaign( id: int, name: string, description: string ) = 
    member val Id = id with get, set
    member val name = name
    member val description = description

这样,所有支持字段都保留在内部,不会出现混淆。缺点是您必须将每个字段写两次:首先作为构造函数参数,然后作为 class 成员。

正如 Fyodor 所指出的,这是由 F# 在您创建记录类型时生成支持字段的方式引起的。 RavenDB 的默认合同解析器序列化该支持字段而不是 public 属性.

You can change the default contract resolver in ravendb. 如果你想使用 Newtonsoft Json.Net:

它看起来像这样
DocumentStore.Conventions.JsonContractResolver <- new CamelCasePropertyNamesContractResolver()

这里有一个解释为什么这有效 here(请参阅标题为:"The explanation" 的部分)。简而言之,Newtonsoft 库使用类型的 public 属性而不是私有支持字段。

我还建议,不要在 Id 上使用 mutable 属性,您可以将 [<CLIMutable>] 属性放在类型本身上,例如:

[<CLIMutable>]
type Campaign = { Id : string; name : string; description : string }

这使得库可以改变值,同时防止它出现在您的代码中。