本章汇集了多年来大家积累的一些实用技巧与心得,由萨宾娜·马纳撰写。
如何通过id查询对象
若已知_id值,可将其初始化为OID并进行查询。
Person selectOne: {('_id' -> (OID value: 16r55CDD2B6E9A87A520F000001))} asDictionary.
请注意以下两种方式是等效的:
OID value: 26555050698940995562836590593. "dec"
OID value: 16r55CDD2B6E9A87A520F000001. "hex"
或者,如果您拥有一个根集合中的实例(本例中为Person实例),可以直接获取其voyageId并用于查询。以下示例假设存在Trips和Persons两个根集合:行程数据中包含嵌入的收据集合,而收据具有description属性。该查询将检索指定人员所有至少包含一张描述为aString的收据的行程记录。
TripselectMany:{('receipts.description' -> aString).('person._id' -> aPerson voyageId)} asDictionary
尚未支持的Mongo命令
indexes
目前尚无法通过Voyage直接创建和删除索引,但您可以使用OSProcess来实现。
假设您有一个名为myDB的数据库,其中包含名为Trips的集合。行程数据中嵌有收据集合,而收据具有名为description的属性。此时您可以通过以下方式在description字段上创建索引:
OSProcess command:
'/{pathToMongoDB}/MongoDB/bin/mongo --eval "db.getSiblingDB(''myDB'').Trips.createIndex({''receipts.description'':1})"'
通过以下命令删除Trips集合中的所有索引:
OSProcess command:
'/{pathToMongoDB}/MongoDB/bin/mongo --eval "db.getSiblingDB(''myDB'').Trips.dropIndexes()"'
Backup
目前尚无法通过Voyage直接创建备份,请使用
OSProcess command:'/{pathToMongoDB}/MongoDB/bin/mongodump --out {BackupPath}'
请参阅MongoDB文档了解相关命令,特别是--eval命令的使用方法。
有用的mongo命令
在mongo控制台中使用“.explain()”来确认查询是否真正使用了索引。
例如:
在嵌入式属性(description)上创建索引:
> db.Trips.createIndex({"receipts.description":1})
执行查询并调用explain方法。可见仅扫描了2个文档:
db.Trips.find({"receipts.description":"a"}).explain("executionStats")
{
"cursor" : "BtreeCursor receipts.receiptDescription_1",
"isMultiKey" : true,
"n" : 2,
"nscannedObjects" : 2,
"nscanned" : 2,
"nscannedObjectsAllPlans" : 2,
"nscannedAllPlans" : 2,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"receipts.receiptDescription" : [
["a", "a"]
]
},
"allPlans" : [
{
"cursor" : "BtreeCursor receipts.receiptDescription_1",
"n" : 2,
"nscannedObjects" : 2,
"nscanned" : 2,
"indexBounds" : {
"receipts.receiptDescription" : [
["a", "a"]
]
}
}
],
"server" : "MacBook-Pro-Sabine.local:27017"
}
现在,删除索引
> db.Trips.dropIndexes()
{"nIndexesWas" : 2,"msg" : "non-_id indexes dropped for collection","ok" : 1
}
再次执行查询,此时会扫描所有文档。
> db.Trips.find({"receipts.receiptDescription":"a"}).explain("executionStats")
{"cursor" : "BasicCursor","isMultiKey" : false,"n" : 2,"nscannedObjects" : 246,"nscanned" : 246,"nscannedObjectsAllPlans" : 246,"nscannedAllPlans" : 246,"scanAndOrder" : false,"indexOnly" : false,"nYields" : 0,"nChunkSkips" : 0,"millis" : 1,"indexBounds" : {},"allPlans" : [{"cursor" : "BasicCursor","n" : 2,"nscannedObjects" : 246,"nscanned" : 246,"indexBounds" : {}}],"server" : "MacBook-Pro-Sabine.local:27017"
}
在MongoDB中存储Date类型的实例
MongoDB存在一个已知问题:无法区分Date和DateAndTime类型。因此即使您存储的是Date类型,查询返回的将是DateAndTime类型。在具体化对象时,您需要手动将其转换回Date类型。
数据库设计
对象结构通常不会形成简单的树形关系,而是包含循环引用的图结构。例如,人员对象可能指向其行程记录,而每个行程记录又关联到对应的人员(形成Person<->>Trip双向关系)。通过分别为Persons和Trips创建根集合,可以避免生成无限循环(详见第1.2章说明)。
这是一个行程指向另一个根集合中人员对象的示例,同时还涉及另一个根集合paymentMethod。需要注意的是,收据也反向指向行程,但这并不会形成循环引用。
Trip
{"_id" : ObjectId("55cf2bc73c9b0fe702000008"),
"#version" : 876079653,
"person" : {"#collection" : "Persons","_id" : ObjectId("55cf2bbb3c9b0fe702000007") },
"receipts" : [{ "currency" : "EUR","date" : { "#instanceOf" : "ZTimestamp", "jdn" : 2457249, "secs" : 0 },"exchangeRate" : 1,"paymentMethod" : {"#collection" : "PaymentMethods","_id" : ObjectId("55cf2bbb3c9b0fe702000003") },"receiptDescription" : "Taxi zum Hotel","receiptNumber" : 1,"trip" : {"#collection" : "Trips","_id" : ObjectId("55cf2bc73c9b0fe702000008") } } ],"startPlace" : "Österreich","tripName" : "asdf","tripNumber" : 1 }
对应的人员对象会指向其所有行程记录以及所属公司
{ "#version" : 714221829,
"_id" : ObjectId("55cf2bbb3c9b0fe702000007"),
"bankName" : "","company" : {
"#collection" : "Companies",
"_id" : ObjectId("55cf2bbb3c9b0fe702000002") },
"email" : "bb@spesenfuchs.de",
"firstName" : "Berta",
"lastName" : "Block",
"roles" : [ "user" ],
"tableOfAccounts" : "SKR03",
"translator" : "German",
"trips" : [
{
"#collection" : "Trips",
"_id" : ObjectId("55cf2bc73c9b0fe702000008") } ] }
如果您的业务领域存在严格划分的区域(例如客户管理),可以考虑为每个区域(客户)创建独立的存储库。
数据检索
问题是:能否从Mongo集合中检索即使该数据库并非通过Voyage创建的数据?答案是肯定的。以下是解决方案。
首先我们创建一个包含两个类方法的MyClass:
MyClass class >> isVoyageRoot^ true
MyClass class >> descriptionContainer<voyageContainer>^ VOContainer newcollectionName: 'myCollection';yourself
此外,为了正确读取数据,需要根据数据库中的内容添加相应的实例变量。
例如,若数据库中存在以下存储信息:
{ "_id" : ObjectId("5900a0175bc65a2b7973b48a"), "item" : "canvas", "qty" : 100, "tags" : [ "cotton" ] }
此时MyClass应包含实例变量:item、qty、tags及相应的访问方法。随后我们在类端定义以下描述:
MyClass class >> mongoItem<mongoDescription>^ VOToOneDescription newattributeName: 'item';kind: String;yourself
MyClass class >> mongoQty<mongoDescription>^ VOToOneDescription newattributeName: 'qty';kind: Integer;yourself
MyClass class >> mongoTags<mongoDescription>^ VOToOneDescription newattributeName: 'tags';kind: OrderedCollection;yourself
此后便可连接数据库并获取信息。
| repository |
repository := VOMongoRepository database: 'databaseName'.
repository selectAll: MyClass