如何从MongoDB中的ChangeStream过滤特定字段的更新

时间:2018-04-03 14:54:38

标签: c# mongodb mongodb-.net-driver

我正在设置一个ChangeStream,以便在集合中更改文档时通知我,这样我就可以将#34; LastModified"该文件的元素到事件发生的时间。由于此更新将导致在ChangeStream上发生新事件,因此我需要过滤掉这些更新以防止无限循环(更新LastModified元素,因为LastModified元素刚刚更新...)。

当我指定确切的字段时,我有以下代码:

ChangeStreamOptions options = new ChangeStreamOptions();
options.ResumeAfter = resumeToken;

string filter = "{ $and: [ { operationType: { $in: ['replace','insert','update'] } }, { 'updateDescription.updatedFields.LastModified': { $exists: false } } ] }";
var pipeline = new EmptyPipelineDefinition<ChangeStreamDocument<BsonDocument>>().Match(filter);

var cursor = collection.Watch(pipeline, options, cancelToken);

但是,我想提供一个我不希望存在于updatedFields文档中的元素名称列表,而不是硬编码&#34; updateDescription.updatedFields.LastModified&#34;。 / p>

我试过了:

string filter = "{ $and: [ { operationType: { $in: ['replace','insert','update'] } }, { 'updateDescription.updatedFields': { $nin: [ 'LastModified' ] } } ] }";

但是这并没有按预期工作(我仍然得到了LastModified更改的更新事件。

我最初使用的是Filter Builder:

FilterDefinitionBuilder<ChangeStreamDocument<BsonDocument>> filterBuilder = Builders<ChangeStreamDocument<BsonDocument>>.Filter;
FilterDefinition<ChangeStreamDocument<BsonDocument>> filter = filterBuilder.In("operationType", new string[] { "replace", "insert", "update" });  //Only include the change if it was one of these types.  Available types are: insert, update, replace, delete, invalidate
filter &= filterBuilder.Nin("updateDescription.updatedFields", ChangedFieldsToIgnore); //If this is an update, only include it if the field(s) updated contains 1+ fields not in the ChangedFieldsToIgnore list

其中ChangedFieldsToIgnore是一个List,其中包含我不想获取事件的字段名称。

任何人都可以帮助我使用我需要的语法吗?或者我是否需要在我的ChangedFieldsToIgnore列表周围创建一个循环,并在过滤器中为每个项目创建一个新条目为&#34; $ exists:false&#34;? (这看起来效率不高)。

修改

我根据@ wan-bachtiar的回答尝试了以下代码,但我在我的枚举器上获得了一个异常.MoveNext()调用:

var match1 = new BsonDocument { { "$match", new BsonDocument { { "operationType", new BsonDocument { { "$in", new BsonArray(new string[] { "replace", "insert", "update" }) } } } } } };
var match2 = new BsonDocument { { "$addFields", new BsonDocument { { "tmpfields", new BsonDocument { { "$objectToArray", "$updateDescription.updatedFields" } } } } } };
var match3 = new BsonDocument { { "$match", new BsonDocument { { "tmpfields.k", new BsonDocument { { "$nin", new BsonArray(updatedFieldsToIgnore) } } } } } };
var pipeline = new[] { match1, match2, match3 };

var cursor = collection.Watch<ChangeStreamDocument<BsonDocument>>(pipeline, options, Profile.CancellationToken);
enumerator = cursor.ToEnumerable().GetEnumerator();

enumerator.MoveNext();
ChangeStreamDocument<BsonDocument> doc = enumerator.Current;

例外情况是:"{"Invalid field name: \"tmpfields\"."}"

我怀疑问题可能是我得到了#34;替换&#34;和&#34;插入&#34;不包含updateDescription字段的事件,因此$ addFields / $ objectToArray失败。我太新了,无法弄清楚语法,但我想我需要使用一个过滤器:

{ $match: { "operationType": { $in: ["replace", "insert"] } } }
OR
{ $eq: { "operationTYpe": "update" }} AND { $addFields....}

此外,似乎C#驱动程序不包含帮助$ addFields和$ objectToArray操作的Builder。我只能使用new BsonDocument {...}方法来构建管道变量。

2 个答案:

答案 0 :(得分:1)

  

ChangedFieldsToIgnore是一个List,其中包含我不想获取事件的字段名称。

如果您想基于多个键进行过滤(updatedFields是否包含某些字段),则首先将键转换为值会更容易。

您可以使用聚合运算符$objectToArrayupdatedFields中包含的文档转换为值。例如:

pipeline = [{"$addFields": {
             "tmpfields":{
               "$objectToArray":"$updateDescription.updatedFields"}
            }}, 
            {"$match":{"tmpfields.k":{
                       "$nin":["LastModified", "AnotherUnwantedField"]}}}
];

上述聚合管道添加了一个名为tmpfields的临时字段。此新字段会将updateDescription.updatedFields转为{name:value}的内容转为[{k:name, v:value}]。一旦我们将这些键作为值,我们就可以将$nin用作过滤器数组。

<强>已更新

您获得tmpfields无效的例外的原因是因为结果已投放到ChangeStreamDocument模型中,该模型没有名为tmpfields的可识别字段。

在这种情况下,当不同的操作没有字段updateDescription.updatedFields时,tmpfields的值只会是null

以下是使用MongoDB .Net driver v2.5的MongoDB ChangeStream .Net / C#的示例,以及修改输出更改流的聚合管道。

此示例不是类型安全的,并将返回BsonDocument

var database = client.GetDatabase("database");            
var collection = database.GetCollection<BsonDocument>("collection");

var options = new ChangeStreamOptions { FullDocument = ChangeStreamFullDocumentOption.UpdateLookup };

// Aggregation Pipeline
var addFields = new BsonDocument { 
                    { "$addFields", new BsonDocument { 
                       { "tmpfields", new BsonDocument { 
                         { "$objectToArray", 
                           "$updateDescription.updatedFields" } 
                       } } 
                 } } };
var match = new BsonDocument { 
                { "$match", new BsonDocument { 
                  { "tmpfields.k", new BsonDocument { 
                    { "$nin", new BsonArray{"LastModified", "Unwanted"} } 
            } } } } };

var pipeline = new[] { addFields, match };

// ChangeStreams
var cursor = collection.Watch<BsonDocument>(pipeline, options);

foreach (var change in cursor.ToEnumerable())
{
    Console.WriteLine(change.ToJson());
}

答案 1 :(得分:0)

我遇到了同样的问题,因此在下面编写了这段代码。无需弄乱BsonObjects ...

//The operationType can be one of the following: insert, update, replace, delete, invalidate
//ignore the field lastrun as we would end in an endles loop
var pipeline = new EmptyPipelineDefinition<ChangeStreamDocument<ATask>>()
    .Match("{ operationType: { $in: [ 'replace', 'update' ] } }")
    .Match(@"{ ""updateDescription.updatedFields.LastRun"" : { $exists: false } }")
    .Match(@"{ ""updateDescription.updatedFields.IsRunning"" : { $exists: false } }");

var options = new ChangeStreamOptions { FullDocument = ChangeStreamFullDocumentOption.UpdateLookup };
var changeStream = Collection.Watch(pipeline, options);    

while (changeStream.MoveNext())
{
    var next = changeStream.Current;
    foreach (var obj in next)
        yield return obj.FullDocument;
}