关于 mongodb:具有反应式 mongo 和 Scala 的嵌套文档

Nested document with reactive mongo and Scala

我正在尝试通过 Scala 将嵌套文档存储在 MongoDB 中。文档看起来像:

1
2
3
4
5
6
7
8
9
10
11
12
Project {
   "_id": ObjectId("528547370cf6e41449003512"),
   "highLevelCode": NumberLong(3),
   "description": [
        {"_id": ObjectId("528547370cf6e41449003521"),
       "lang":"en",
       "desc":"desc in English"},
        {"_id": ObjectId("528547370cf6e41449003522"),
       "lang":"fr",
       "desc":"desc en francais"}],
   "budget": NumberLong(12345)
}

基本上我想在项目文档中存储嵌套描述,可以是多种语言。

我写的代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import reactivemongo.bson._
import reactivemongo.bson.handlers.{BSONWriter, BSONReader}
import reactivemongo.bson.BSONLong
import reactivemongo.bson.BSONString

case class LocaleText(
  id: Option[BSONObjectID],
  lang: String,
  textDesc: String
)

object LocaleText {
  implicit object LocaleTextBSONReader extends BSONReader[LocaleText] {
    def fromBSON(document: BSONDocument): LocaleText = {
      val doc = document.toTraversable

      LocaleText(
        doc.getAs[BSONObjectID]("_id"),
        doc.getAs[BSONString]("lang").map(_.value).get,
        doc.getAs[BSONString]("textDesc").map(_.value).get
      )
    }
  }

  implicit object LocaleTextBSONWriter extends BSONWriter[LocaleText] {
    def toBSON(localText: LocaleText) = {
      BSONDocument(
       "_id" -> localText.id.getOrElse(BSONObjectID.generate),
       "lang" -> BSONString(localText.lang),
       "textDesc" -> BSONString(localText.textDesc)
      )
    }
  }    
}

case class Project(
  id:                Option[BSONObjectID],
  description:          List[LocaleText],
  budget:               Option[Long]
  )

object Project {

  implicit object ProjectReader extends BSONReader[Project]{
    def fromBSON(doc: BSONDocument): Project = {
      val document = doc.toTraversable

      Project(
        document.getAs[BSONObjectID]("_id"),
        document.getAs[BSONArray]("description").map { values =>
            values.values.toList.flatMap { case value =>
              value match {
                case v: LocaleText => Some(v.asInstanceOf[LocaleText])
                case _ => None
              }
            }
        }.getOrElse(List.empty),
        document.getAs[BSONLong]("budget").map(_.value)
      )
    }
  }

  implicit object ProjectWriter extends BSONWriter[Project]{
    def toBSON(project: Project): BSONDocument = {
      BSONDocument(
       "_id"                   -> project.id.getOrElse(BSONObjectID.generate),
   "description"           -> BSONArray(project.description)
      ).append(Seq(
        project.budget.map(b =>"budget" -> BSONLong(b))
      ).flatten:_*)
    }
  }
}

但是,它给了我像

这样的编译错误

重载方法值适用于替代方案:[错误](生产者:reactivemongo.bson.Implicits.Producer[(String,reactivemongo.bson.BSONValue)],生产者:reactivemongo.bson.Implicits.Producer[(String,reactivemongo. bson.BSONValue)])reactivemongo.bson.AppendableBSONDocument
[错误] (els: (String, reactivemongo.bson.BSONValue))reactivemongo.bson.AppendableBSONDocument
[错误] 不能应用于 ((String, reactivemongo.bson.BSONObjectID), List[LocaleText])...

基本上 Scala 不喜欢这条线
"description" -> BSONArray(project.description)

但是,尽管我不能使用列表/数组来允许两种以上的语言,但以下替代方法仍然有效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
case class LocaleText(
  enDesc: String,
  frDesc: String)

case class Project(
  id:                   Option[BSONObjectID],
  description:          LocaleText)

object Project {
implicit object LocaleTextBSONReader extends BSONReader[LocaleText] {
    def fromBSON(document: BSONDocument): LocaleText = {
      val doc = document.toTraversable

      LocaleText(
        doc.getAs[BSONString]("enDesc").map(_.value).get,
        doc.getAs[BSONString]("frDesc").map(_.value).get
      )
    }
  }

  implicit object LocaleTextBSONWriter extends BSONWriter[LocaleText] {
    def toBSON(localText: LocaleText) = {
      BSONDocument(
       "enDesc" -> BSONString(localText.enDesc),
       "frDesc" -> BSONString(localText.frDesc)
      )
    }
  }    

implicit object ProjectReader extends BSONReader[Project]{
def fromBSON(doc: BSONDocument): Project = {
  val document = doc.toTraversable

  Project(
    document.getAs[BSONObjectID]("_id"),
    document.getAs[BSONString]("iatiId").map(_.value).get,
    LocaleTextBSONReader.fromBSON(document.getAs[BSONDocument]("description").get)
  }
}

implicit object ProjectWriter extends BSONWriter[Project]{
def toBSON(project: Project): BSONDocument = {
  BSONDocument(
   "_id"                   -> project.id.getOrElse(BSONObjectID.generate),
   "iatiId"                -> BSONString(project.iatiId),
   "description"           -> LocaleTextBSONWriter.toBSON(project.description)
 }
}

如何将 Project.description 转换为 Mongo 的 LocaleText 列表到 BSONArray?如果您能对我的问题有所了解,我将不胜感激。非常感谢您的帮助。


最后我找到了我自己的问题的解决方案,希望这能帮助其他一些在 ReactiveMongo 0.8 上苦苦挣扎的人:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
case class LocaleText(
    lang: String,
    desc: String)

case class Project(
    id:                 Option[BSONObjectID],
    descriptions:         List[LocaleText])

object Project {
  implicit object LocaleTextBSONReader extends BSONReader[LocaleText] {
        def fromBSON(document: BSONDocument): LocaleText = {
          val doc = document.toTraversable

          LocaleText(
            doc.getAs[BSONString]("lang").get.value,
            doc.getAs[BSONString]("desc").get.value
          )
        }
      }

      implicit object LocaleTextBSONWriter extends BSONWriter[LocaleText] {
        def toBSON(localText: LocaleText) = {
          BSONDocument(
           "lang" -> BSONString(localText.lang),
           "desc" -> BSONString(localText.desc)
          )
        }
      }    

  implicit object ProjectReader extends BSONReader[Project]{
    def fromBSON(doc: BSONDocument): Project = {
      val document = doc.toTraversable

      Project(
        document.getAs[BSONObjectID]("_id"),
        document.getAs[BSONArray]("descriptions").get.toTraversable.toList.map { descText =>
            LocaleTextBSONReader.fromBSON(descText.asInstanceOf[TraversableBSONDocument]
        }
      )
    }
  }

  implicit object ProjectWriter extends BSONWriter[Project]{
    def toBSON(project: Project): BSONDocument = {
      BSONDocument(
       "_id"                   -> project.id.getOrElse(BSONObjectID.generate),
       "descriptions"          -> BSONArray(project.descriptions.map {
        description => LocaleTextBSONWriter.toBSON(description)
      }: _*)
    }
  }

这可能是图书馆的问题。我使用最新版本的 reactivemongo 测试了你的代码,它编译得很好(我需要调整你的代码以适应 BSONReaders 和 BSONWriters 的新语法,但这不应该对错误产生任何影响)。

使用 reactivemongo 0.10.0 你甚至可以使用新提供的宏:

1
2
3
4
5
6
7
8
9
10
11
12
13
import reactivemongo.bson._

case class LocaleText(id: Option[BSONObjectID], lang: String, textDesc: String)

object LocaleText {
  implicit val localeTextBSONHandler = Macros.handler[LocaleText]
}

case class Project(id: Option[BSONObjectID], description: List[LocaleText], budget: Option[Long])

object Project {
  implicit val projectBSONHandler = Macros.handler[Project]
}