To ensure your database adheres to your application design, you can strategically create indexes to combine index properties with schema validation.为了确保数据库符合应用程序设计,您可以策略性地创建索引,将索引属性与模式验证相结合。
About this Task关于此任务
Consider an application that summarizes a user’s finances. The main page of the application displays the user’s ID and the balances on all of their banking accounts synced with the application.考虑一个总结用户财务状况的应用程序。应用程序的主页显示用户的ID以及与应用程序同步的所有银行账户的余额。
The application stores its user information in a collection called 应用程序将其用户信息存储在名为users. The users collection contains documents with the following schema:users的集合中。users集合包含具有以下架构的文档:
db.users.insertOne( {
_id: 1,
name: { first: "john", last: "smith" },
accounts: [
{ balance: 500, bank: "abc", number: "123" },
{ balance: 2500, bank: "universal bank", number: "9029481" }
]
} )
The application requires the following rules:应用程序需要以下规则:
A user can register in the application and not sync a bank account.用户可以在应用程序中注册,而不同步银行帐户。A user identifies an account by its用户通过bankandnumberfields.bank和number字段识别帐户。A user cannot register the same account for two different users.一个用户不能为两个不同的用户注册同一个帐户。A user cannot register the same account multiple times for the same user.用户不能为同一用户多次注册同一帐户。
To design your database so that it confines its documents to the application’s rules, combine a unique index and schema validation on your database using the following procedure.要设计数据库,使其将文档限制在应用程序的规则范围内,请使用以下过程在数据库上组合唯一索引和模式验证。
Steps步骤
Create a multi-property index创建多属性索引
To enforce the application’s rules, create an index on the 要执行应用程序的规则,请在accounts.bank and accounts.number fields with the following characteristics:accounts.bank和accounts.number字段上创建具有以下特征的索引:
To ensure the为确保bankandnumberfields do not repeat, make the index unique.bank和number字段不重复,请使索引唯一。To allow indexing of multiple fields, make the index compound.要允许对多个字段进行索引,请使索引复合。To allow indexing of documents inside an array, make the index of the type multikey.要允许对数组中的文档进行索引,请将索引类型设置为多键。
You therefore create a compound multikey unique index with the following specification and options:因此,您可以使用以下规范和选项创建复合多键唯一索引:
const specification = { "accounts.bank": 1, "accounts.number": 1 };
const options = { name: "Unique Account", unique: true };
db.users.createIndex(specification, options); // Unique Account唯一帐户Create a 创建partialFilterExpression
The index in its current state indexes all documents. However, this implementation can cause errors when you insert documents missing the 当前状态的索引对所有文档进行索引。但是,当您插入缺少accounts.bank or accounts.number fields.accounts.bank或accounts.number字段的文档时,此实现可能会导致错误。
For example, try to insert the following data into the 例如,尝试将以下数据插入到users collection:users集合中:
const user1 = { _id: 1, name: { first: "john", last: "smith" } };
const user2 = { _id: 2, name: { first: "john", last: "appleseed" } };
const account1 = { balance: 500, bank: "abc", number: "123" };
db.users.insertOne(user1);
db.users.insertOne(user2);
{ acknowledged: true, insertedId: 1 }
MongoServerError: E11000 duplicate key error collection: test.users index: Unique Account dup key: { accounts.bank: null, accounts.number: null }
When you try to insert a document that is missing one or more specified fields into an indexed collection, MongoDB:当您尝试将缺少一个或多个指定字段的文档插入索引集合时,MongoDB:
populates the missing fields into the inserted document将缺失的字段填充到插入的文档中sets their values to将它们的值设置为nullnulladds an entry to the index向索引中添加条目
When you insert 当你插入没有user1 without the accounts.bank and accounts.number fields, MongoDB sets them to null and adds a unique index entry. Any later insert that also lacks either field, such as user2, causes a duplicate key error.accounts.bank和accounts.number字段的user1时,MongoDB会将它们设置为null并添加一个唯一的索引条目。任何后续插入也缺少任一字段,如user2,都会导致重复键错误。
To avoid this, use a partial filter expression so the index only includes documents that contain both fields. For more information, see Partial Index with Unique Constraint. Recreate the index using the following options:为了避免这种情况,请使用部分筛选表达式,这样索引就只包括包含这两个字段的文档。有关详细信息,请参阅具有唯一约束的部分索引。使用以下选项重新创建索引:
const specification = { "accounts.bank": 1, "accounts.number": 1 };
const optionsV2 = {
name: "Unique Account V2",
partialFilterExpression: {
"accounts.bank": { $exists: true },
"accounts.number": { $exists: true }
},
unique: true
};
db.users.drop( {} ); // Delete previous documents and indexes definitions删除以前的文档和索引定义
db.users.createIndex(specification, optionsV2); // Unique Account V2唯一帐户V2
Test out your new index definition by inserting two users that do not contain the fields 通过插入两个不包含accounts.bank and accounts.number:accounts.bank和accounts.number字段的用户来测试新的索引定义:
db.users.insertOne(user1);
db.users.insertOne(user2);
{ acknowledged: true, insertedId: 1 }
{ acknowledged: true, insertedId: 2 }
Test your database implementation测试数据库实现
To ensure that you cannot register the same account for two different users, test the following code:为了确保您不能为两个不同的用户注册同一个帐户,请测试以下代码:
/* Cleaning the collection清理集合 */
db.users.deleteMany( {} ); // Delete only documents, keep indexes definitions仅删除文档,保留索引定义
db.users.insertMany( [user1, user2] );
/* Test */
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.updateOne( { _id: user2._id }, { $push: { accounts: account1 } } );
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
MongoServerError: E11000 duplicate key error collection: test.users index: Unique Account V2 dup key: { accounts.bank: "abc", accounts.number: "123" }
The second 第二个updateOne command correctly returns an error, since you cannot add the same account for two separate users.updateOne命令正确返回错误,因为您不能为两个单独的用户添加相同的帐户。
Test that the database does not allow you to add the same account multiple times for the same user:测试数据库是否允许您为同一用户多次添加同一帐户:
/* Cleaning the collection清理集合 */
db.users.deleteMany( {} ); // Delete only documents, keep indexes definitions仅删除文档,保留索引定义
db.users.insertMany( [user1, user2] ); // Re-insert test documents重新插入测试文件
/* Test */
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.findOne( { _id: user1._id } );
{ acknowledged: true, insertedIds: { '0': 1, '1': 2 } }
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
_id: 1,
name: { first: 'john', last: 'smith' },
accounts: [
{ balance: 500, bank: 'abc', number: '123' },
{ balance: 500, bank: 'abc', number: '123' }
]
The returned code shows that the database incorrectly adds the same account multiple times to the same user. This error occurs because MongoDB indexes do not duplicate strictly equal entries with the same key values pointing to the same document.返回的代码显示数据库错误地将同一帐户多次添加到同一用户。发生此错误是因为MongoDB索引不会重复具有指向同一文档的相同键值的严格相等条目。
When you insert 当您在用户上第二次插入account1 for the second time on the user, MongoDB does not create an index entry, so there are no duplicate values on it. To effectively implement your application design, your database should return an error if you attempt to add the same account multiple times to the same user.account1时,MongoDB不会创建索引条目,因此上面没有重复的值。为了有效地实现应用程序设计,如果您尝试将同一帐户多次添加到同一用户,数据库应该返回错误。
Set up schema validation设置架构验证
To make your application reject adding the same account multiple times to the same user, implement Schema Validation. 要使应用程序拒绝将同一帐户多次添加到同一用户,请实现架构验证。The following code uses the 以下代码使用$expr operator to write an expression to verify if the items inside an array are unique:$expr运算符编写表达式,以验证数组中的项是否唯一:
const accountsSet = {
$setIntersection: {
$map: {
input: "$accounts",
in: { bank: "$$this.bank", number: "$$this.number" }
}
}
};
const uniqueAccounts = {
$eq: [ { $size: "$accounts" }, { $size: accountsSet } ]
};
const accountsValidator = {
$expr: {
$cond: {
if: { $isArray: "$accounts" },
then: uniqueAccounts,
else: true
}
}
};
When 当{ $isArray: "$accounts" } is true, then the accounts array exists in a document, and MongoDB applies the uniqueAccounts validation logic. If the document passes the logic, it is valid.{ $isArray: "$accounts" }为true时,accounts数组存在于文档中,MongoDB应用uniqueAccounts验证逻辑。如果文档通过了逻辑,则它是有效的。
The uniqueAccounts expression compares the size of the original accounts array to the size of accountsSet, which is created by the $setIntersection of a mapped version of accounts:uniqueAccounts表达式将原始accounts数组的大小与accountsSet的大小进行比较,accountsSet是由映射版本的accounts的$setIntersection创建的:
The$mapfunction transforms each entry in theaccountsarray to include only theaccounts.bankandaccounts.numberfields.$map函数将accounts数组中的每个条目转换为仅包含account.bank和account.number字段。The$setIntersectionfunction removes duplicates by treating the mapped array as a set.$setIntersection函数通过将映射数组视为一个集合来删除重复项。The$eqfunction compares the size of the originalaccountsarray and the deduplicatedaccountsSet.$eq函数比较原始accounts数组和已消除重复的accountsSet的大小。
If both sizes are equal, all entries are unique by 如果两个大小相等,则所有条目在accounts.bank and accounts.number, then the validation returns true. If not, duplicates are present, and validation fails with an error.accounts.bank和accounts.number上都是唯一的,则验证返回true。如果没有,则存在重复项,验证失败并出现错误。
You can test out your schema validation to ensure your database does not allow adding the same account multiple times to the same user:您可以测试模式验证,以确保数据库不允许将同一帐户多次添加到同一用户:
/* Cleaning the collection清理集合 */
db.users.drop( {} ); // Delete documents and indexes definitions删除文档和索引定义
db.runCommand( {
collMod: "users", // update collection to use schema validation更新集合以使用架构验证
validator: accountsValidator
} );
db.users.insertMany( [user1, user2] );
/* Test */
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
db.users.updateOne( { _id: user1._id }, { $push: { accounts: account1 } } );
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: 1,
details: {
operatorName: '$expr',
specifiedAs: {
'$expr': {
'$cond': {
if: { '$and': '$accounts' },
then: { '$eq': [ [Object], [Object] ] },
else: true
}
}
},
reason: 'expression did not match',
expressionResult: false
}
}
The second 第二个updateOne() command returns a Document failed validation error, indicating that the database now rejects any attempt to add the same account multiple times to the same user.updateOne()命令返回Document failed validation错误,表示数据库现在拒绝向同一用户多次添加同一帐户的任何尝试。