将MongoDB文档映射到具有类型但没有嵌入式文档的案例类 [英] Mapping MongoDB documents to case class with types but without embedded documents
问题描述
子集看起来像是一个有趣的瘦MongoDB包装器.
Subset looks like an interesting, thin MongoDB wrapper.
在给出的示例之一中,有Tweets和Users.但是,User
是Tweet
的子文档.在经典SQL中,这将被标准化为两个单独的表,并具有从Tweet到User的外键.在MongoDB中,这不需要DBRef
,存储用户的ObjectId
就足够了.
In one of the examples given, there are Tweets and Users. However, User
is a subdocument of Tweet
. In classical SQL, this would be normalized into two separate tables with a foreign key from Tweet to User. In MongoDB, this wouldn't necessitate a DBRef
, storing the user's ObjectId
would be sufficient.
在Subset和Salat中,这都将导致以下案例类:
Both in Subset and Salat this would result in these case classes:
case class Tweet(_id: ObjectId, content: String, userId: ObjectId)
case class User(_id: ObjectId, name: String)
因此,不能保证Tweet中的ObjectId实际上解析为User(使它的类型安全性降低).我还必须为每个引用User的类编写相同的查询(或将其移至某些特征).
So there's no guarantee that the ObjectId in Tweet actually resolves to a User (making it less typesafe). I also have to write the same query for each class that references User (or move it to some trait).
所以我想实现的是在代码中包含case class Tweet(_id: ObjectId, content: String, userId: User)
,在数据库中包含ObjectId
.这可能吗?如果可以,怎么办?有什么好的选择?
So what I'd like to achieve is to have case class Tweet(_id: ObjectId, content: String, userId: User)
, in code, and the ObjectId
in the database. Is this possible, and if so, how? What are good alternatives?
推荐答案
是的,有可能.实际上,它甚至比在"tweet"中包含"user"子文档还要简单.当用户"是一个引用时,它只是一个标量值,MongoDB和子集"没有查询子文档字段的机制.
Yes, it's possible. Actually it's even simpler than having a "user" sub-document in a "tweet". When "user" is a reference, it is just a scalar value, MongoDB and "Subset" has no mechanisms to query subdocument fields.
我为您准备了一个简单的可替换代码段(假定您有两个集合-"tweets"和"users").
I've prepared a simple REPLable snippet of code for you (it assumes you have two collections -- "tweets" and "users").
准备...
import org.bson.types.ObjectId
import com.mongodb._
import com.osinka.subset._
import Document.DocumentId
val db = new Mongo("localhost") getDB "test"
val tweets = db getCollection "tweets"
val users = db getCollection "users"
我们的User
案例类
case class User(_id: ObjectId, name: String)
tweets和用户的许多字段
A number of fields for tweets and user
val content = "content".fieldOf[String]
val user = "user".fieldOf[User]
val name = "name".fieldOf[String]
更复杂的事情开始发生.我们需要的是能够根据字段名称获取ObjectId
的ValueReader
,然后转到另一个集合并从那里读取对象.
Here more complicated things start to happen. What we need is a ValueReader
that's capable of getting ObjectId
based on field name, but then goes to another collection and reads an object from there.
这可以写成一个单一的代码,一次完成所有事情(您可能会在回答历史中看到这样的变体),但是将其表达为读者的组合会更加习惯.假设我们有一个ValueReader[User]
从DBObject
读取:
This can be written as a single piece of code, that does all things at once (you may see such a variant in the answer history), but it would be more idiomatic to express it as a combination of readers. Suppose we have a ValueReader[User]
that reads from DBObject
:
val userFromDBObject = ValueReader({
case DocumentId(id) ~ name(name) => User(id, name)
})
剩下的是通用ValueReader[T]
,它期望ObjectId
并使用提供的底层阅读器从特定集合中检索对象:
What's left is a generic ValueReader[T]
that expects ObjectId
and retrieves an object from a specific collection using supplied underlying reader:
class RefReader[T](val collection: DBCollection, val underlying: ValueReader[T]) extends ValueReader[T] {
override def unpack(o: Any):Option[T] =
o match {
case id: ObjectId =>
Option(collection findOne id) flatMap {underlying.unpack _}
case _ =>
None
}
}
然后,我们可以说我们从引用中读取User
的类型类仅仅是
Then, we may say our type class for reading User
s from references is merely
implicit val userReader = new RefReader[User](users, userFromDBObject)
(对于这个问题,我深表感谢,因为这个用例非常 很少见,我没有开发通用解决方案的真正动力.我认为 最后,我需要将这种帮助程序包括在子集"中. 非常感谢您对这种方法的反馈)
(I am grateful to you for this question, since this use case is quite rare and I had no real motivation to develop a generic solution. I think I need to include this kind of helper into "Subset" finally.. I shall appreciate your feedback on this approach)
这是您将如何使用它:
And this is how you would use it:
import collection.JavaConverters._
tweets.find.iterator.asScala foreach {
case Document.DocumentId(id) ~ content(content) ~ user(u) =>
println("%s - %s by %s".format(id, content, u))
}
这篇关于将MongoDB文档映射到具有类型但没有嵌入式文档的案例类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!