/// <summary> /// Asynchronously sets data in the document, either replacing it completely or merging fields. /// </summary> /// <param name="documentData">The data to store in the document. Must not be null.</param> /// <param name="options">The options to use when updating the document. May be null, which is equivalent to <see cref="SetOptions.Overwrite"/>.</param> /// <param name="cancellationToken">A cancellation token to monitor for the asynchronous operation.</param> /// <returns>The write result of the server operation.</returns> public async Task <WriteResult> SetAsync(object documentData, SetOptions options = null, CancellationToken cancellationToken = default) { var batch = Database.StartBatch(); batch.Set(this, documentData, options); var results = await batch.CommitAsync(cancellationToken).ConfigureAwait(false); return(results[0]); }
/// <summary> /// Adds an operation to set a document's data in this transaction. /// </summary> /// <param name="documentReference">The document in which to set the data. Must not be null.</param> /// <param name="documentData">The data for the document. Must not be null.</param> /// <param name="options">The options to use when updating the document. May be null, which is equivalent to <see cref="SetOptions.Overwrite"/>.</param> public void Set(DocumentReference documentReference, object documentData, SetOptions options = null) { // Preconditions are validated by WriteBatch. _writes.Set(documentReference, documentData, options); }
/// <summary> /// Adds an operation that sets data in a document, either replacing it completely or merging fields. /// </summary> /// <param name="documentReference">A document reference indicating the path of the document to update. Must not be null.</param> /// <param name="documentData">The data to store in the document. Must not be null.</param> /// <param name="options">The options to use when setting data in the document. May be null, which is equivalent to <see cref="SetOptions.Overwrite"/>.</param> /// <returns>This batch, for the purposes of method chaining.</returns> public WriteBatch Set(DocumentReference documentReference, object documentData, SetOptions options = null) { GaxPreconditions.CheckNotNull(documentReference, nameof(documentReference)); GaxPreconditions.CheckNotNull(documentData, nameof(documentData)); var fields = ValueSerializer.SerializeMap(documentData); options = options ?? SetOptions.Overwrite; var sentinels = FindSentinels(fields); var deletes = sentinels.Where(sf => sf.IsDelete).ToList(); var nonDeletes = sentinels.Where(sf => !sf.IsDelete).ToList(); bool forceWrite = false; IDictionary <FieldPath, Value> updates; IReadOnlyList <FieldPath> updatePaths; if (options.Merge) { var mask = options.FieldMask; if (mask.Count == 0) { // Merge all: // - If the data is empty, we force a write // - Deletes are allowed anywhere // - All timestamps converted to transforms // - Each top-level entry becomes a FieldPath forceWrite = fields.Count == 0; RemoveSentinels(fields, nonDeletes); // Work out the update paths after removing server timestamps but before removing deletes, // so that we correctly perform the deletes. updatePaths = ExtractDocumentMask(fields); RemoveSentinels(fields, deletes); updates = fields.ToDictionary(pair => new FieldPath(pair.Key), pair => pair.Value); } else { // Merge specific: // - Deletes must be in the mask // - Only timestamps in the mask are converted to transforms // - Apply the field mask to get the updates GaxPreconditions.CheckArgument(deletes.All(sf => mask.Contains(sf.FieldPath)), nameof(documentData), "Delete cannot appear in an unmerged field"); nonDeletes = nonDeletes.Where(sf => mask.Any(fp => fp.IsPrefixOf(sf.FieldPath))).ToList(); RemoveSentinels(fields, deletes); RemoveSentinels(fields, nonDeletes); updates = ApplyFieldMask(fields, mask); // Every field path in the mask must either refer to a now-removed sentinel, or a remaining value. // Sentinels are permitted to be in the mask in a nested fashion rather than directly, e.g. a mask of "parent" with a sentinel of "parent.child.timestamp" is fine. GaxPreconditions.CheckArgument( mask.All(p => updates.ContainsKey(p) || deletes.Any(sf => p.IsPrefixOf(sf.FieldPath)) || nonDeletes.Any(sf => p.IsPrefixOf(sf.FieldPath))), nameof(documentData), "All paths specified for merging must appear in the data."); updatePaths = mask; } } else { // Overwrite: // - No deletes allowed // - All timestamps converted to transforms // - Each top-level entry becomes a FieldPath GaxPreconditions.CheckArgument(deletes.Count == 0, nameof(documentData), "Delete cannot appear in document data when overwriting"); RemoveSentinels(fields, nonDeletes); updates = fields.ToDictionary(pair => new FieldPath(pair.Key), pair => pair.Value); updatePaths = s_emptyFieldPathList; forceWrite = true; } AddUpdateWrites(documentReference, ExpandObject(updates), updatePaths, null, nonDeletes, forceWrite, options.Merge); return(this); }
/// <summary> /// Adds an operation that sets data in a document, either replacing it completely or merging fields. /// </summary> /// <param name="documentReference">A document reference indicating the path of the document to update. Must not be null.</param> /// <param name="documentData">The data to store in the document. Must not be null.</param> /// <param name="options">The options to use when setting data in the document. May be null, which is equivalent to <see cref="SetOptions.Overwrite"/>.</param> /// <returns>This batch, for the purposes of method chaining.</returns> public WriteBatch Set(DocumentReference documentReference, object documentData, SetOptions options = null) { GaxPreconditions.CheckNotNull(documentReference, nameof(documentReference)); GaxPreconditions.CheckNotNull(documentData, nameof(documentData)); var fields = ValueSerializer.SerializeMap(documentData); options = options ?? SetOptions.Overwrite; var serverTimestamps = new List <FieldPath>(); var deletes = new List <FieldPath>(); FindSentinels(fields, FieldPath.Empty, serverTimestamps, deletes); bool forceWrite = false; IDictionary <FieldPath, Value> updates; IReadOnlyList <FieldPath> updatePaths; if (options.Merge) { var mask = options.FieldMask; if (mask.Count == 0) { // Merge all: // - Empty data is not allowed // - Deletes are allowed anywhere // - All timestamps converted to transforms // - Each top-level entry becomes a FieldPath GaxPreconditions.CheckArgument(fields.Count != 0, nameof(documentData), "{0} cannot be specified with empty data", nameof(SetOptions.MergeAll)); RemoveSentinels(fields, serverTimestamps); // Work out the update paths after removing server timestamps but before removing deletes, // so that we correctly perform the deletes. updatePaths = ExtractDocumentMask(fields); RemoveSentinels(fields, deletes); updates = fields.ToDictionary(pair => new FieldPath(pair.Key), pair => pair.Value); } else { // Merge specific: // - Deletes must be in the mask // - Only timestamps in the mask are converted to transforms // - Apply the field mask to get the updates GaxPreconditions.CheckArgument(deletes.All(p => mask.Contains(p)), nameof(documentData), "Delete cannot appear in an unmerged field"); serverTimestamps = serverTimestamps.Intersect(mask).ToList(); RemoveSentinels(fields, deletes); RemoveSentinels(fields, serverTimestamps); updates = ApplyFieldMask(fields, mask); // Every field path in the mask must either refer to a now-removed sentinel, or a remaining value. GaxPreconditions.CheckArgument(mask.All(p => updates.ContainsKey(p) || deletes.Contains(p) || serverTimestamps.Contains(p)), nameof(documentData), "All paths specified for merging must appear in the data."); updatePaths = mask; } } else { // Overwrite: // - No deletes allowed // - All timestamps converted to transforms // - Each top-level entry becomes a FieldPath GaxPreconditions.CheckArgument(deletes.Count == 0, nameof(documentData), "Delete cannot appear in document data when overwriting"); RemoveSentinels(fields, serverTimestamps); updates = fields.ToDictionary(pair => new FieldPath(pair.Key), pair => pair.Value); updatePaths = s_emptyFieldPathList; forceWrite = true; } AddUpdateWrites(documentReference, ExpandObject(updates), updatePaths, null, serverTimestamps, forceWrite); return(this); }