如何使用 C# 驱动程序更新 MongoDB 数组中的子文档

Posted

技术标签:

【中文标题】如何使用 C# 驱动程序更新 MongoDB 数组中的子文档【英文标题】:How to update child document in MongoDB Array using C# Driver 【发布时间】:2017-12-26 10:26:27 【问题描述】:

如何修改MongoDB文档子数组中的子元素?我正在使用最新的 C# MongoDB 驱动程序(C# 驱动程序版本:2.5,服务器版本:3.2.0),我已经尝试了来自多个答案的解决方案SO(也有重复的问题)和其他各种来源。大多数解决方案都被证明是过时的。

以下是我的示例文档:

[ 
"_id" : "69c4f77d-05ef-431d-ae17-c076c6173e04",     
"title" : "Root Page", 
"createdAt" : ISODate("2017-12-21T12:28:00.680+0000"), 
"modifiedAt" : ISODate("2017-12-26T07:18:11.165+0000"), 
"pages" : [
    
        "_id" : "f449dc0b-3d1b-4a59-b622-6a42ce10b147", 
        "title" : "Test page 1", 
        "slug" : "test-page-1", 
        "createdAt" : ISODate("2017-12-21T12:28:00.680+0000"), 
        "modifiedAt" : ISODate("2017-12-21T12:28:00.680+0000")
    , 
    
        "_id" : "3d1497d7-f74c-4d88-b15c-bf2f9c736374", 
        "title" : "Test page 2", 
        "slug" : "test-page-2", 
        "createdAt" : ISODate("2017-12-25T11:27:55.006+0000"), 
        "modifiedAt" : ISODate("2017-12-25T11:27:55.006+0000")
    , 
    
        "_id" : "6e827e2a-5a25-4343-b646-885816bb8cc4", 
        "title" : "Test page 3", 
        "slug" : "test-page-3", 
        "createdAt" : ISODate("2017-12-25T11:31:16.516+0000"), 
        "modifiedAt" : ISODate("2017-12-25T11:31:16.516+0000")
    ]
,
...,
...
]

文档结构: 根文档包含一组子文档,其中包含元数据和页面列表(数组)

在子文档中 - 我正在尝试更新 pageschild page 对象的 titleslug /strong> 数组,但到目前为止还没有最新的驱动程序。

我正在使用以下过滤器和更新查询:

        var filter = Builders<SubDocument>.Filter.And(Builders<SubDocument>.Filter.Eq("_id", "xxx"), Builders<SubDocument>.Filter.ElemMatch(x => x.Pages, p => p.Id == "xxx"));

        var update = Builders<SubDocument>.Update
                        .Set("pages.$.title", "changed title")
                        .Set("pages.$.slug", "changed-title")
                        .Set("pages.$.modifiedAt", DateTime.UtcNow);

        _mongoDb.Documents.UpdateOne(filter, update);

现在 - 这个查询返回一个错误:

无效的 BSON 字段名称 pages.$.title

我过去几个小时的参考资料说要使用“$”运算符,但它会在最新的驱动程序中引发错误。

[编辑 1]这是我的模型:

public class SubDocument

    public SubDocument()
    
        Id = Guid.NewGuid().ToString();
        CreatedAt = DateTime.UtcNow;
        ModifiedAt = DateTime.UtcNow;
        Pages = new List<Page>();
    

    [BsonId]
    public string Id  get; set;         

    [BsonElement("title")]
    public string Title  get; set;         

    [BsonElement("createdAt")]
    public DateTime CreatedAt  get; set; 

    [BsonElement("modifiedAt")]
    public DateTime ModifiedAt  get; set; 

    [BsonElement("pages")]
    public List<Page> Pages  get; set; 




public class Page

    [BsonId]
    public string Id  get; set; 

    [BsonElement("title")]
    public string Title  get; set; 

    [BsonElement("slug")]
    public string Slug  get; set; 

    [BsonElement("createdAt")]
    public DateTime CreatedAt  get; set; 

    [BsonElement("modifiedAt")]
    public DateTime ModifiedAt  get; set; 


有人吗?提前致谢!

【问题讨论】:

您需要更新哪个元素以及更新到哪个值?还是所有页面?你有过滤器吗? 嗨@Evk,我已经更新了问题,PS,谢谢! 您还可以为您的子文档包含模型吗?只是为了方便复制粘贴 无论如何,尝试这样做:.Set(c =&gt; c.Pages[-1].Title, "changed title")(其他字段也一样)。 您发布的相同代码(以及我的建议)适用于我的驱动程序版本 2.5 和 mongo 版本 3.4 【参考方案1】:

您的代码确实适用于 mongod v3.4 和驱动程序 v2.5。

正如 Evk 在 cmets 中所建议的,这里是代码的强类型版本,它对重构很友好。

var filter = Builders<SubDocument>.Filter
    .And(
        Builders<SubDocument>.Filter.Eq(d => d.Id, "69c4f77d-05ef-431d-ae17-c076c6173e04"), 
        Builders<SubDocument>.Filter.ElemMatch(x => x.Pages, p => p.Id == "3d1497d7-f74c-4d88-b15c-bf2f9c736374"));

var update = Builders<SubDocument>.Update
                .Set(c => c.Pages[-1].Title, "changed title")
                .Set(c => c.Pages[-1].Slug, "changed slug")
                .Set(c => c.Pages[-1].ModifiedAt, DateTime.UtcNow);

subDocumentsCollection.UpdateOne(filter, update);

另一种方法可能会将您的子文档实体视为一个整体(面向文档的数据库鼓励这种方法),因此始终完全更新实体并避免细粒度更新(在 R-DBMS 的情况下更有可能)。在这种情况下,你可能会写这样的东西。

var document = subDocumentsCollection
    .Find(d => d.Id == "69c4f77d-05ef-431d-ae17-c076c6173e04")
    .Single();

var page = document.Pages.Single(p => p.Id == "3d1497d7-f74c-4d88-b15c-bf2f9c736374");

page.Title = "changed title";
page.Slug = "changed slug";
page.ModifiedAt = DateTime.UtcNow;

subDocumentsCollection.ReplaceOne(d => d.Id == document.Id, document);

第二种方法有以下优点和缺点。

专业人士

更新发生在域层(可能通过非常合适的方法),从而允许执行所有域规则并防止脏数据进入数据库;因此它更面向对象。 由于代码是面向领域的,它更简单,并且不依赖于数据库内部。

缺点

这种方法需要对数据库进行两次点击(一次用于读取,一次用于更新); 如果您的实体很大,网络必须传输比严格需要更多的数据。

考虑到过早的优化是编程中万恶之源(或至少是大部分) (here),一般来说我会选择第二种方法,回退到第一个仅在性能要求非常严格或网络带宽较低的情况下使用。

【讨论】:

@Marcello- 很好的答案 @Marcello,您的示例似乎更新了父文档 - 这可能会发生冲突。我是不是误会了? @Chazt3n 是的。无论如何,第一种方法也可能导致更新冲突(例如,两个用户同时更新子文档中的page.Title)。在第二种方法中,潜在的冲突范围更广。如果出现此类并发问题,应使用锁定策略(例如these)。 我需要更新符合条件的所有孩子?

以上是关于如何使用 C# 驱动程序更新 MongoDB 数组中的子文档的主要内容,如果未能解决你的问题,请参考以下文章

Mongodb 如何使用 C# 驱动程序更新许多文档

如何使用官方 c# 驱动程序在 MongoDB 中使用 Update.Set 更新多个字段?

如何在 MongoDB C# 驱动程序版本 2 中使用 $ 位置运算符

如何在 MongoDB C# 驱动程序版本 2 中使用 $ 位置运算符

如何使用 C# mongodb 驱动程序防止 SQL 注入?

如何使用C#驱动程序更新MongoDB数组中的子文档