EasilyNET.Mongo 6.26.813.102

dotnet add package EasilyNET.Mongo --version 6.26.813.102
                    
NuGet\Install-Package EasilyNET.Mongo -Version 6.26.813.102
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EasilyNET.Mongo" Version="6.26.813.102" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EasilyNET.Mongo" Version="6.26.813.102" />
                    
Directory.Packages.props
<PackageReference Include="EasilyNET.Mongo" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EasilyNET.Mongo --version 6.26.813.102
                    
#r "nuget: EasilyNET.Mongo, 6.26.813.102"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EasilyNET.Mongo@6.26.813.102
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EasilyNET.Mongo&version=6.26.813.102
                    
Install as a Cake Addin
#tool nuget:?package=EasilyNET.Mongo&version=6.26.813.102
                    
Install as a Cake Tool

EasilyNET.Mongo

为 .NET 应用(ASP.NET Core / Worker Service / 控制台 / 通用 Host 均可)提供完整的 MongoDB 集成方案,涵盖连接注册、序列化、自动索引(含 Atlas Search / Vector Search)、变更流、GridFS 文件存储、固定集合/时序集合自动创建、健康检查等开箱即用能力。

📦 包重命名:本包由 EasilyNET.Mongo.AspNetCore 重命名而来,已不再依赖 ASP.NET Core 共享框架,可用于任意 .NET 宿主。迁移要点:

  1. 包名 EasilyNET.Mongo.AspNetCoreEasilyNET.Mongo
  2. 命名空间 EasilyNET.Mongo.AspNetCore.*EasilyNET.Mongo.*(注册扩展仍位于 Microsoft.Extensions.DependencyInjection,通常无需改 using);
  3. 集合/索引初始化由中间件 app.UseCreateMongoXxx<T>() 改为服务注册 builder.Services.AddMongoXxxCreation<T>()(详见下文各章节)。

目录


安装

dotnet add package EasilyNET.Mongo

⚠️ 中断性变更(Breaking Changes)

不再注册 IMongoClientIMongoDatabase 到 DI 容器

此前版本会将 IMongoClientIMongoDatabase 注册为单例服务,可直接通过 DI 注入。当前版本已移除此行为。

请通过 MongoContext 子类实例的 ClientDatabase 属性访问:

// ❌ 旧方式(不再支持)
public class MyService(IMongoClient client, IMongoDatabase database) { }

// ✅ 新方式:通过 DbContext 获取
public class MyService(MyDbContext db)
{
    // 访问 IMongoClient
    var client = db.Client;

    // 访问 IMongoDatabase
    var database = db.Database;

    // 直接使用集合
    var orders = db.Orders;
}

Convention 配置方式变更

Convention 相关配置已从 ClientOptions / BasicClientOptions 中移除,改为通过独立的 ConfigureMongoConventions 方法全局配置:

// ❌ 旧方式(不再支持)
builder.Services.AddMongoContext<MyDbContext>(config, c =>
{
    c.DefaultConventionRegistry = true;
    c.ObjectIdToStringTypes = [typeof(SomeEntity)];
    c.ConventionRegistry = new() { ... };
});

// ✅ 新方式:Convention 独立配置,在 AddMongoContext 之前调用
builder.Services.ConfigureMongoConventions(c =>
{
    c.ObjectIdToStringTypes = [typeof(SomeEntity)];
    c.AddConvention("myConvention", new ConventionPack { ... });
});
builder.Services.AddMongoContext<MyDbContext>(config, c =>
{
    c.DatabaseName = "mydb";
});

若不调用 ConfigureMongoConventions,首次 AddMongoContext 时将自动使用默认配置(驼峰命名 + 忽略未知字段 + _id 映射 + 枚举存字符串)。


快速开始:注册 MongoContext

首先继承 MongoContext 定义自己的数据库上下文(详见 EasilyNET.Mongo.Core 文档):

public class MyDbContext : MongoContext
{
    public IMongoCollection<Order> Orders { get; set; } = default!;
    public IMongoCollection<User> Users { get; set; } = default!;
}

方式 1:使用 IConfiguration(推荐)

appsettings.json 中配置连接字符串:

{
  "ConnectionStrings": {
    "Mongo": "mongodb://localhost:27017/mydb"
  }
}
var builder = WebApplication.CreateBuilder(args);

// 可选:自定义全局 Convention(必须在 AddMongoContext 之前调用,最多一次)
// 调用后仅使用用户自定义的约定,本库内置默认约定不会被应用
// 若不调用,首次 AddMongoContext 时自动使用默认配置
builder.Services.ConfigureMongoConventions(c =>
{
    // 特定类型的 ObjectId 存为 string(在使用 $unwind 时有时需要此配置)
    c.ObjectIdToStringTypes = [typeof(SomeEntity)];

    // 添加自定义约定包(支持链式调用)
    c.AddConvention("default", new ConventionPack
    {
        new CamelCaseElementNameConvention(),
        new IgnoreExtraElementsConvention(true),
        new NamedIdMemberConvention("Id", "ID"),
        new EnumRepresentationConvention(BsonType.String)
    })
    .AddConvention("ignoreDefault", new ConventionPack
    {
        new IgnoreIfDefaultConvention(true)
    });
});

builder.Services.AddMongoContext<MyDbContext>(builder.Configuration, c =>
{
    // 数据库名称(可选,覆盖连接字符串中的库名)
    c.DatabaseName = "mydb";

    // 高级驱动配置(可选,如对接 APM 探针)
    c.ClientSettings = cs =>
    {
        cs.ClusterConfigurator = cb => cb.Subscribe(new DiagnosticsActivityEventSubscriber());
    };
});

方式 2:使用连接字符串

builder.Services.AddMongoContext<MyDbContext>("mongodb://localhost:27017/mydb", c =>
{
    c.DatabaseName = "mydb";
});

方式 3:使用 MongoClientSettings

builder.Services.AddMongoContext<MyDbContext>(
    new MongoClientSettings
    {
        Servers = [new MongoServerAddress("127.0.0.1", 27017)],
        Credential = MongoCredential.CreateCredential("admin", "username", "password")
    },
    c =>
    {
        c.DatabaseName = "mydb";
    });

弹性连接配置

MongoDB 驱动内置连接自动恢复,Resilience 提供开箱即用的默认超时与连接池配置,与驱动自带机制协同工作:

builder.Services.AddMongoContext<MyDbContext>(builder.Configuration, c =>
{
    // 启用弹性默认值(与驱动内置自动恢复机制配合)
    c.Resilience.Enable = true;

    // 以下均为可选调整,括号内为默认值
    c.Resilience.ServerSelectionTimeout = TimeSpan.FromSeconds(10); // 服务器选择超时
    c.Resilience.ConnectTimeout = TimeSpan.FromSeconds(10);         // TCP 连接建立超时
    c.Resilience.SocketTimeout = TimeSpan.FromSeconds(60);          // Socket 读写超时
    c.Resilience.WaitQueueTimeout = TimeSpan.FromMinutes(1);        // 连接池等待超时
    c.Resilience.HeartbeatInterval = TimeSpan.FromSeconds(10);      // 心跳检测间隔
    c.Resilience.MaxConnectionPoolSize = 100;                       // 最大连接数
    c.Resilience.MinConnectionPoolSize = null;                      // 最小连接数(null = 使用驱动默认值)
    c.Resilience.RetryReads = true;                                 // 自动重试读操作
    c.Resilience.RetryWrites = true;                                // 自动重试写操作
});

场景化建议

场景 推荐配置
同区域低延迟 ConnectTimeout=5s, ServerSelectionTimeout=5s
跨区域高延迟 ConnectTimeout=20s, ServerSelectionTimeout=30s
高并发大流量 MaxConnectionPoolSize=200+, WaitQueueTimeout=30s
代理 / 单节点 连接串加 directConnection=trueloadBalanced=true
Atlas / 云托管 保持默认值,确保 RetryReads=true, RetryWrites=true

⚠️ 弹性配置不能解决网络/认证/拓扑错误,请先排查连接串配置。


字段映射与命名约定

默认情况下(未调用 ConfigureMongoConventions),框架会自动注册以下规则:

功能 说明 示例
驼峰字段名 C# PascalCase → MongoDB camelCase PageSizepageSize
_id 映射 自动将 _id 与实体中的 Id / ID 属性互相映射 _idId
枚举存字符串 枚举值以字符串形式存储,便于阅读 Gender.Male"Male"
忽略未知字段 反序列化时忽略数据库中存在但代码中未定义的字段 向前兼容
DateTime 本地化 DateTime 反序列化后自动设为 DateTimeKind.Local 时区一致
Decimal128 decimal 类型自动映射为 MongoDB Decimal128,避免精度丢失 金额字段

自定义序列化器

AddMongoContext 之后,通过扩展方法注册序列化器。

DateOnly / TimeOnly

官方新版驱动已支持,本库额外提供字符串和 Ticks 两种存储方式以兼容历史数据:

// 字符串格式(默认 "yyyy-MM-dd" 和 "HH:mm:ss.ffffff",便于阅读和查询)
builder.Services.RegisterSerializer(new DateOnlySerializerAsString());
builder.Services.RegisterSerializer(new TimeOnlySerializerAsString());

// 自定义格式
builder.Services.RegisterSerializer(new DateOnlySerializerAsString("yyyy/MM/dd"));

// Ticks 格式(long 类型,节省空间,适合高频时间字段)
builder.Services.RegisterSerializer(new DateOnlySerializerAsTicks());
builder.Services.RegisterSerializer(new TimeOnlySerializerAsTicks());

⚠️ 同一类型全局只能注册一种方案,String 和 Ticks 方式不能同时注册。

动态 / JSON 类型

// object、dynamic、匿名类型支持
builder.Services.RegisterDynamicSerializer();

// System.Text.Json 的 JsonNode / JsonObject 类型
builder.Services.RegisterSerializer(new JsonNodeSerializer());
builder.Services.RegisterSerializer(new JsonObjectSerializer());

⚠️ JsonNode 反序列化不支持 Unicode 转义字符;若需要跨系统序列化,请将 JsonSerializerOptions.Encoder 设为 JavaScriptEncoder.UnsafeRelaxedJsonEscaping

枚举键字典

// 支持 Dictionary<TEnum, TValue> / IDictionary<TEnum, TValue> / IReadOnlyDictionary<TEnum, TValue>
builder.Services.RegisterGlobalEnumKeyDictionarySerializer();

其他

builder.Services.RegisterSerializer(new DoubleSerializer(BsonType.Double));
builder.Services.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));

自动创建索引

通过 [MongoIndex] / [MongoCompoundIndex] 特性声明索引(见 EasilyNET.Mongo.Core 文档),然后在注册服务时调用:

// 注册托管后台服务:应用启动后自动扫描所有带索引特性的实体,
// 使用异步驱动 API 创建/更新索引(不阻塞应用启动),完成后结束。
builder.Services.AddMongoIndexCreation<MyDbContext>();

框架会:

  1. 扫描 MyDbContext 的所有 IMongoCollection<T> 属性
  2. 比对数据库中的现有索引与代码声明
  3. 创建缺失的索引;对于结构变更的索引,优先尝试“先建后删”,冲突时回退为“删后建”

索引管理策略

默认情况下,框架只创建/更新代码中声明的索引,不会删除数据库中手动创建的索引(安全模式)。如需自动清理未在代码中声明的索引:

builder.Services.AddMongoContext<MyDbContext>(builder.Configuration, c =>
{
    // 启用自动删除未管理的索引(谨慎!会删除 DBA 手动创建的索引)
    c.DropUnmanagedIndexes = true;

    // 保护特定前缀的索引不被删除(即使 DropUnmanagedIndexes = true)
    c.ProtectedIndexPrefixes.Add("dba_");      // 保护 DBA 手动创建的索引
    c.ProtectedIndexPrefixes.Add("analytics_"); // 保护分析用索引
});

⚠️ DropUnmanagedIndexes 在生产环境请谨慎使用。建议配合 ProtectedIndexPrefixes 保护重要索引。

⚠️ 时序集合(TimeSeries)上的时间字段由 MongoDB 自动索引,框架会自动跳过这些字段。


自动创建时序集合

什么是时序集合? 时序集合(Time Series Collection,MongoDB 5.0+)采用列式内部存储,针对时间递增数据(传感器、监控指标、行情数据)提供极高的压缩比和查询性能。

通过 [TimeSeriesCollection] 特性标记实体(详见 EasilyNET.Mongo.Core 文档):

[TimeSeriesCollection(
    collectionName: "sensor_readings",
    timeField: "timestamp",      // 时间字段(DateTime 类型)
    metaField: "deviceId",       // 分组字段(传感器ID、设备ID 等)
    granularity: TimeSeriesGranularity.Seconds)]
public class SensorReading
{
    public string Id { get; set; }
    public DateTime Timestamp { get; set; }
    public string DeviceId { get; set; }
    public double Temperature { get; set; }
}

在注册服务时启用自动创建(托管后台服务,启动后异步执行,不阻塞启动):

// 若集合不存在则自动创建,已存在则跳过
builder.Services.AddMongoTimeSeriesCollectionCreation<MyDbContext>();

⚠️ 时序集合一旦创建,timeField/metaField 不可修改。system.profile 是保留名称不能使用。


自动创建固定大小集合

什么是固定大小集合? Capped Collection 类似环形缓冲区,存储达到上限后最老的文档自动被覆盖,天然维护“最近 N 条”语义,写入性能极高。适用于操作日志、审计记录、消息暂存等场景。

通过 [CappedCollection] 特性标记(详见 EasilyNET.Mongo.Core 文档):

// 保存最近 100MB 的操作日志
[CappedCollection(collectionName: "operation_logs", maxSize: 100 * 1024 * 1024)]
public class OperationLog
{
    public string Id { get; set; }
    public DateTime CreatedAt { get; set; }
    public string Action { get; set; }
}

// 同时限制大小和条数(二者均满足才覆盖旧数据)
[CappedCollection("audit_logs", maxSize: 50 * 1024 * 1024, MaxDocuments = 50000)]
public class AuditLog
{
    // ...
}
builder.Services.AddMongoCappedCollectionCreation<MyDbContext>();

⚠️ Capped 集合不支持删除单个文档(只能 drop 整个集合)。


Atlas Search / Vector Search 索引自动创建

Atlas Search 是基于 Apache Lucene 的全文搜索引擎,支持中文分词、相关性排序、自动补全。Vector Search 是 AI 语义搜索的核心能力,广泛用于 RAG(检索增强生成)场景。

通过 [MongoSearchIndex][SearchField][VectorField][VectorFilterField][AutoEmbeddingField] 特性声明(详见 EasilyNET.Mongo.Core 文档):

[MongoSearchIndex(Name = "product_search")]
[MongoSearchIndex(Name = "product_vector", Type = ESearchIndexType.VectorSearch, StoredSource = true)]
public class Product
{
    public string Id { get; set; }

    // 中文全文搜索 + 自动补全
    [SearchField(ESearchFieldType.String, IndexName = "product_search",
        AnalyzerName = "lucene.chinese")]
    [SearchField(ESearchFieldType.Autocomplete, IndexName = "product_search",
        AnalyzerName = "lucene.chinese")]
    public string Name { get; set; }

    // 1536 维向量(对应 OpenAI text-embedding-ada-002)
    // Quantization: 自动向量量化(Scalar 约省 4 倍内存,Binary 约省 32 倍)
    // MaxEdges / NumEdgeCandidates: HNSW 图调优参数(0 = 使用服务端默认值)
    [VectorField(Dimensions = 1536, Similarity = EVectorSimilarity.Cosine,
        IndexName = "product_vector", Quantization = EVectorQuantization.Scalar)]
    public float[] Embedding { get; set; }

    // 自动嵌入:Atlas 在索引和查询时自动调用 Voyage AI 模型生成向量,无需自己维护嵌入管道
    [AutoEmbeddingField("voyage-4", IndexName = "product_vector")]
    public string Description { get; set; }

    // 向量搜索的预过滤字段(只在指定分类中搜索)
    [VectorFilterField(IndexName = "product_vector")]
    public string Category { get; set; }
}

💡 StoredSource = true 会在索引中存储原始文档字段,配合 $vectorSearchreturnStoredSource: true 可以省去搜索后的全文档回表查询。

💡 后台服务会在启动时对比特性声明与服务端的索引定义(latestDefinition):索引不存在则创建; 声明的字段发生变化(如维度、相似度、量化方式)则自动调用 updateSearchIndex 更新(Atlas 后台重建期间旧索引仍可查询)。 仅从特性中移除字段不会触发更新,避免误判导致索引反复重建。

对于未在 MongoContext 上声明为 IMongoCollection<T> 属性的实体类型,可以通过 CollectionName 显式指定集合名称,框架会通过程序集扫描自动发现并创建索引:

// 该实体不需要在 DbContext 中声明,框架通过程序集扫描自动发现
[MongoSearchIndex(Name = "log_search", CollectionName = "application_logs")]
public class ApplicationLog
{
    [SearchField(ESearchFieldType.String, AnalyzerName = "lucene.standard")]
    public string Message { get; set; }

    [SearchField(ESearchFieldType.Date)]
    public DateTime Timestamp { get; set; }
}

💡 如果实体已在 MongoContext 上声明为 IMongoCollection<T> 属性,则无需设置 CollectionName,集合名称会自动解析。

在服务注册阶段添加后台服务(启动后异步创建,不阻塞应用启动):

builder.Services.AddMongoSearchIndexCreation<MyDbContext>();

需要 MongoDB Atlas 或 MongoDB 8.2+ 社区版。不支持的环境会记录警告并跳过,不影响应用启动。

在代码中执行向量搜索:

// 先用 AI 模型生成查询向量,再进行语义搜索
var queryVector = await embeddingService.GetEmbeddingAsync("蓝牙耳机 降噪");

var pipeline = new BsonDocument[]
{
    new("$vectorSearch", new BsonDocument
    {
        { "index", "product_vector" },
        { "path", "embedding" },
        { "queryVector", new BsonArray(queryVector.Select(f => (BsonValue)f)) },
        { "numCandidates", 150 },
        { "limit", 10 },
        { "filter", new BsonDocument("category", "电子产品") }  // 预过滤
    })
};

var results = await db.Products.Aggregate<BsonDocument>(pipeline).ToListAsync();

变更流(Change Stream)

MongoDB 变更流是实时数据订阅机制,可监听集合的插入、更新、删除等操作。常用于:

  • 跨系统数据同步:将变更实时同步到 Elasticsearch、Redis 等
  • 事件驱动架构:数据变更触发下游业务(如发送通知、更新缓存)
  • 审计追踪:自动记录所有数据变更历史

⚠️ 要求:变更流需要 MongoDB 副本集(Replica Set)或 Atlas。

1. 定义变更流处理器

继承 MongoChangeStreamHandler<TDocument>,实现 HandleChangeAsync

using EasilyNET.Mongo.ChangeStreams;
using EasilyNET.Mongo.Options;

public class OrderChangeStreamHandler : MongoChangeStreamHandler<Order>
{
    private readonly IServiceScopeFactory _scopeFactory;

    public OrderChangeStreamHandler(
        MyDbContext db,
        ILogger<OrderChangeStreamHandler> logger,
        IServiceScopeFactory scopeFactory)
        : base(db.Database, collectionName: "orders", logger, new ChangeStreamHandlerOptions
        {
            // 持久化恢复令牌:应用重启后从上次位置继续,不丢失事件
            PersistResumeToken = true,
            ResumeTokenCollectionName = "_changeStreamResumeTokens", // 令牌存储集合

            // 断线重连:指数退避策略
            MaxRetryAttempts = 5,                         // 最大重试次数(0 = 无限)
            RetryDelay = TimeSpan.FromSeconds(2),         // 初始间隔(2→4→8→16→32s)
            MaxRetryDelay = TimeSpan.FromSeconds(60),     // 最大间隔

            // FullDocument 策略(更新事件是否拉取完整文档)
            FullDocument = ChangeStreamFullDocumentOption.UpdateLookup,
        })
    {
        _scopeFactory = scopeFactory;
    }

    // 只监听插入和更新(不设置则监听全部操作类型)
    protected override ChangeStreamOperationType[]? WatchOperations =>
    [
        ChangeStreamOperationType.Insert,
        ChangeStreamOperationType.Update,
        ChangeStreamOperationType.Replace
    ];

    protected override async Task HandleChangeAsync(
        ChangeStreamDocument<Order> change,
        CancellationToken cancellationToken)
    {
        // 处理器本身是 Singleton,Scoped 服务需通过 Scope 获取
        using var scope = _scopeFactory.CreateScope();
        var notifier = scope.ServiceProvider.GetRequiredService<INotificationService>();

        switch (change.OperationType)
        {
            case ChangeStreamOperationType.Insert:
                await notifier.SendNewOrderAlertAsync(change.FullDocument, cancellationToken);
                break;

            case ChangeStreamOperationType.Update when change.FullDocument?.Status == "shipped":
                await notifier.SendShippedNotificationAsync(change.FullDocument, cancellationToken);
                break;
        }
    }
}

2. 注册处理器

// 注册为后台服务,应用启动时自动开始监听
builder.Services.AddMongoChangeStreamHandler<OrderChangeStreamHandler>();

ChangeStreamHandlerOptions 参数说明

属性 默认值 说明
MaxRetryAttempts 5 断线后最大重试次数,0 表示无限重试
RetryDelay 2s 首次重试间隔,后续每次翻倍
MaxRetryDelay 60s 重试间隔上限
PersistResumeToken false 是否将恢复令牌持久化到 MongoDB
ResumeTokenPersistInterval 0(每个事件都写) 令牌持久化写入的最小间隔,高变更量场景可设为如 5s 以限流写入
ResumeTokenCollectionName "_changeStreamResumeTokens" 存储恢复令牌的集合名
FullDocument UpdateLookup 更新事件是否返回完整文档

断点续传工作原理

开启 PersistResumeToken = true 后:

事件 1 → HandleChangeAsync() → 保存 Token-A
事件 2 → HandleChangeAsync() → 保存 Token-B
[应用重启]
从 Token-B 恢复(StartAfter,可跨 invalidate 事件)→ 继续处理事件 3、4、5 ...

⚠️ 投递语义为至少一次:恢复令牌仅在 HandleChangeAsync 成功完成后才推进,出错或重连后同一事件可能被再次投递, 处理逻辑应保证幂等。设置了 ResumeTokenPersistInterval 后,间隔内的令牌保存在内存中, 并在流暂停、出错或关闭时自动刷新写入;崩溃时最多重放一个间隔内的事件。


GridFS 文件存储

GridFS 是 MongoDB 内置的大文件(> 16MB)分片存储方案,适合不引入额外对象存储的简单场景。

注册 GridFS

// 使用默认数据库(集合:fs.files, fs.chunks)
builder.Services.AddGridFSBucket<MyDbContext>();

// 自定义桶名和块大小
builder.Services.AddGridFSBucket<MyDbContext>(opt =>
{
    opt.BucketName = "uploads";      // 集合前缀:uploads.files, uploads.chunks
    opt.ChunkSizeBytes = 512 * 1024;  // 每块 512KB(默认 255KB)
});

// 使用独立的数据库(文件库与业务库分离),通过键控服务注入
builder.Services.AddGridFSBucket<MyDbContext>(
    serviceKey: "media",              // DI 键名,注入时使用 [FromKeyedServices("media")]
    databaseName: "file-storage-db",  // 独立数据库名
    opt =>
    {
        opt.BucketName = "media";
    });

键控注入(多 GridFS 桶场景)

当注册了多个 GridFS 桶时,通过 [FromKeyedServices] 特性注入指定的桶:

// 注册多个桶
builder.Services.AddGridFSBucket<MyDbContext>("media", "media-db");
builder.Services.AddGridFSBucket<MyDbContext>("documents", "docs-db");

// 在服务中注入
public class MediaService([FromKeyedServices("media")] IGridFSBucket mediaBucket)
{
    // mediaBucket 操作 media-db 数据库中的 fs.files / fs.chunks
}

使用 GridFS

public class FileStorageService(IGridFSBucket gridFs)
{
    // 上传文件
    public async Task<string> UploadAsync(string fileName, Stream content, CancellationToken ct = default)
    {
        var options = new GridFSUploadOptions
        {
            Metadata = new BsonDocument
            {
                { "contentType", "image/jpeg" },
                { "uploadedBy", "user_001" }
            }
        };
        var fileId = await gridFs.UploadFromStreamAsync(fileName, content, options, ct);
        return fileId.ToString();
    }

    // 下载文件(按 ID)
    public async Task<Stream> DownloadAsync(string fileId, CancellationToken ct = default)
    {
        var stream = new MemoryStream();
        await gridFs.DownloadToStreamAsync(ObjectId.Parse(fileId), stream, cancellationToken: ct);
        stream.Position = 0;
        return stream;
    }

    // 下载文件(按文件名,取最新版本)
    public async Task<Stream> DownloadByNameAsync(string fileName, CancellationToken ct = default)
    {
        var stream = new MemoryStream();
        await gridFs.DownloadToStreamByNameAsync(fileName, stream, cancellationToken: ct);
        stream.Position = 0;
        return stream;
    }

    // 删除文件
    public async Task DeleteAsync(string fileId, CancellationToken ct = default)
        => await gridFs.DeleteAsync(ObjectId.Parse(fileId), ct);

    // 查询文件信息
    public async Task<GridFSFileInfo?> FindInfoAsync(string fileId, CancellationToken ct = default)
    {
        var filter = Builders<GridFSFileInfo>.Filter.Eq("_id", ObjectId.Parse(fileId));
        using var cursor = await gridFs.FindAsync(filter, cancellationToken: ct);
        return await cursor.FirstOrDefaultAsync(ct);
    }
}

健康检查

将 MongoDB 连通性纳入 ASP.NET Core 健康检查,与 Kubernetes 探针、负载均衡器集成:

builder.Services.AddHealthChecks()
    .AddMongoHealthCheck<MyDbContext>(
        name: "mongodb",                        // 健康检查名称(默认 "mongodb")
        failureStatus: HealthStatus.Unhealthy,   // 失败状态(默认 Unhealthy)
        tags: ["db", "mongodb"],              // 可选标签(用于分组过滤)
        timeout: TimeSpan.FromSeconds(5));       // 超时时间

var app = builder.Build();

// 暴露统一健康检查端点
app.MapHealthChecks("/health");

// Kubernetes:就绪探针(只检查 DB 连通性)
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("db")
});

// Kubernetes:存活探针(只检查进程存活)
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = _ => false
});

健康检查通过向 MongoDB 发送 ping 命令验证连通性,超时或异常则返回 Unhealthy


使用 EasilyNET.AutoDependencyInjection 集成

若使用模块化依赖注入体系,可将 MongoDB 配置封装为独立模块。

安装模块化 DI

dotnet add package EasilyNET.AutoDependencyInjection

创建 Mongo 模块

using EasilyNET.AutoDependencyInjection.Contexts;
using EasilyNET.AutoDependencyInjection.Modules;
using EasilyNET.Mongo.Serializers;
using EasilyNET.Mongo.ConsoleDebug.Subscribers;
using WebApi.Test.Unit.Common;

internal sealed class MongoModule : AppModule
{
    public override void ConfigureServices(ConfigureServicesContext context)
    {
        var config = context.Configuration;
        var env = context.Environment ?? throw new("获取环境信息出错");

        context.Services.AddMongoContext<DbContext>(config, c =>
        {
            c.DatabaseName = "easilynet";
            c.ClientSettings = cs =>
            {
                cs.ClusterConfigurator = s =>
                {
                    if (env.IsDevelopment())
                    {
                        s.Subscribe(new ActivityEventConsoleDebugSubscriber(new()
                        {
                            Enable = false
                        }));
                    }
                    s.Subscribe(new ActivityEventDiagnosticsSubscriber(new()
                    {
                        CaptureCommandText = true
                    }));
                };
                cs.ApplicationName = Constant.InstanceName;
            };
        });

        context.Services.AddMongoContext<DbContext2>(config, c =>
        {
            c.DatabaseName = "easilynet2";
            c.ClientSettings = cs =>
            {
                cs.ClusterConfigurator = cb => cb.Subscribe(new ActivityEventConsoleDebugSubscriber(new()
                {
                    Enable = false
                }));
                cs.ApplicationName = Constant.InstanceName;
            };
        });

        // 序列化器
        context.Services.RegisterSerializer(new DateOnlySerializerAsString());
        context.Services.RegisterSerializer(new TimeOnlySerializerAsString());
        context.Services.RegisterSerializer(new JsonNodeSerializer());
        context.Services.RegisterSerializer(new JsonObjectSerializer());
        context.Services.RegisterDynamicSerializer();
        context.Services.RegisterGlobalEnumKeyDictionarySerializer();
        // 注册索引自动创建的后台服务(启动后异步执行,不阻塞启动)
        context.Services.AddMongoIndexCreation<DbContext>();
        context.Services.AddMongoIndexCreation<DbContext2>();
    }
}

创建根模块

[DependsOn(
    typeof(DependencyAppModule),
    typeof(ResponseCompressionModule),
    typeof(CorsModule),
    typeof(ControllersModule),
    typeof(GarnetDistributedCacheModule),
    typeof(MongoModule)
    // ... 其他模块按中间件顺序继续追加
)]
internal sealed class AppWebModule : AppModule
{
    public override void ConfigureServices(ConfigureServicesContext context)
    {
        context.Services.AddProblemDetails();
        context.Services.AddExceptionHandler<BusinessExceptionHandler>();
        context.Services.AddHttpContextAccessor();
    }

    public override async Task ApplicationInitialization(ApplicationContext context)
    {
        var app = context.GetApplicationHost() as IApplicationBuilder;
        app?.UseExceptionHandler();
        app?.UseResponseTime();
        app?.UseAuthentication();
        app?.UseAuthorization();
        app?.UseStaticFiles();
        await base.ApplicationInitialization(context);
    }
}

Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationModules<AppWebModule>();

var app = builder.Build();
app.InitializeApplication();
app.MapControllers();
app.MapHealthChecks("/health");
app.Run();

常见问题排查

MongoConnectionPoolPausedException: "The connection pool is in paused state"

连接池暂停意味着驱动将服务器标记为不可用。常见原因:

原因 解决方式
网络不可达 / 防火墙拦截 确认应用机器能访问 host:port,安全组已放行
认证或 TLS 错误 检查用户名、密码、authSourcetls 参数
单节点 / 代理访问 连接串加 directConnection=trueloadBalanced=true
副本集名称不匹配 连接串中的 replicaSet 必须与服务端一致
连接池耗尽 降低 MinConnectionPoolSize,合理设置 MaxConnectionPoolSize

推荐在连接串中显式设置超时:

mongodb://user:pwd@host:27017/db?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=30000

变更流报错 "Change stream not supported on Standalone"

变更流需要副本集或 Atlas。本地开发可使用项目提供的 Docker Compose 启动:

docker compose -f docker-compose.mongo.rs.yml up -d

Atlas Search 索引未创建

  1. 确认使用的是 MongoDB Atlas 或 MongoDB 8.2+ 社区版
  2. Atlas 侧索引创建是异步的,通常需要几秒到几分钟
  3. 检查日志中是否有 Failed to ensure search indexes 错误
  4. 确认已在服务注册阶段调用 builder.Services.AddMongoSearchIndexCreation<MyDbContext>()

EasilyNET MongoDB 套件 · 完整使用文档与场景选型指南

本节是对 EasilyNET.MongoEasilyNET.Mongo.CoreEasilyNET.Mongo.ConsoleDebug 三个包的统一汇总, 侧重「有哪些功能 / 什么场景该用哪个 / 怎么用」。如需某个功能的最细节说明,可对照各包自带的 README.md

  • EasilyNET.Mongo.Core/README.md —— 上下文基类、特性、批量写入、聚合、地理空间
  • EasilyNET.Mongo/README.md(即本文件上半部分) —— 服务注册、序列化、自动建索引/集合、变更流、GridFS、健康检查
  • EasilyNET.Mongo.ConsoleDebug/README.md —— 命令行调试输出与 OpenTelemetry 诊断

目录(套件总览)


1. 套件结构与选型

角色 关键依赖 何时引用
EasilyNET.Mongo.Core 核心基础库:MongoContext 基类、全部特性、聚合/批量/地理空间扩展 MongoDB.Driver 仅写实体与查询逻辑、不需要 DI 注册时(如类库项目)。通常被 EasilyNET.Mongo 自动引用,无需单独安装。
EasilyNET.Mongo 宿主集成层:DI 注册、序列化、自动建索引/集合、变更流、GridFS、健康检查 Microsoft.Extensions.Hosting.AbstractionsHealthChecksMongo.Core 任意 .NET 宿主(ASP.NET Core / Worker / 控制台 / 通用 Host)需要开箱即用集成时。绝大多数项目装这个即可。
EasilyNET.Mongo.ConsoleDebug 诊断层:命令输出到控制台 + OpenTelemetry 诊断 MongoDB.DriverSpectre.Console.Json 开发期想看 MongoDB 实际执行命令,或接入 APM/链路追踪时按需引用。
  • 目标框架:net10.0net11.0
  • EasilyNET.Mongo 已不再依赖 ASP.NET Core 共享框架(由 EasilyNET.Mongo.AspNetCore 重命名而来),可用于任意 Host。
dotnet add package EasilyNET.Mongo              # 主包(含 Core)
dotnet add package EasilyNET.Mongo.ConsoleDebug # 可选,调试/诊断

2. 功能 → 场景速查表

你的需求 用什么 注册 / 标记入口 运行环境要求
连接数据库、集中管理集合 继承 MongoContext AddMongoContext<T>(...) 任意
生产环境超时/连接池/重试调优 Resilience 选项 c.Resilience.Enable = true 任意
字段名驼峰、_id↔Id、枚举存字符串 内置约定(默认开启) 不调用即默认;或 ConfigureMongoConventions 任意
DateOnly/TimeOnly/JsonNode/枚举键字典 持久化 自定义序列化器 RegisterSerializer(...) 任意
启动时按特性自动建普通/复合/TTL/地理索引 [MongoIndex] / [MongoCompoundIndex] AddMongoIndexCreation<T>() 任意
固定大小日志/环形缓冲 [CappedCollection] AddMongoCappedCollectionCreation<T>() 任意
时序数据(IoT/监控/行情) [TimeSeriesCollection] AddMongoTimeSeriesCollectionCreation<T>() MongoDB 5.0+
关联查询、分组统计、分桶、多维聚合 聚合扩展 LookupAndUnwindAsync / GroupByCountAsync / BucketAsync / FacetAsync 任意
一次往返完成混合增删改 批量写入 Fluent BulkWriteAsync(bulk => ...) 任意
附近搜索 / 区域筛选 / 含距离排序 地理空间扩展 NearSphere / GeoWithin / GeoNearAsync 2dsphere 索引
实时监听数据变更、跨系统同步、审计 MongoChangeStreamHandler<T> AddMongoChangeStreamHandler<H>() 副本集 / Atlas
大文件存储(>16MB) GridFS AddGridFSBucket<T>(...) 任意
中文全文搜索 / 自动补全 [MongoSearchIndex] + [SearchField] AddMongoSearchIndexCreation<T>() Atlas 或 MongoDB 8.2+
AI 语义搜索 / RAG [MongoSearchIndex(VectorSearch)] + [VectorField] AddMongoSearchIndexCreation<T>() Atlas 或 MongoDB 8.2+
K8s 探针 / 连通性监控 健康检查 AddMongoHealthCheck<T>() 任意
开发期查看执行命令 ConsoleDebug 订阅器 ActivityEventConsoleDebugSubscriber 任意
APM / 分布式链路追踪 OpenTelemetry 订阅器 ActivityEventDiagnosticsSubscriber 任意

3. 最小可用配置(5 分钟上手)

// 1) 定义上下文
public class AppDbContext : MongoContext
{
    public IMongoCollection<Order> Orders { get; set; } = default!;
    public IMongoCollection<User>  Users  { get; set; } = default!;
}

// 2) appsettings.json
//   { "ConnectionStrings": { "Mongo": "mongodb://localhost:27017/mydb" } }

// 3) 注册
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMongoContext<AppDbContext>(builder.Configuration, c =>
{
    c.DatabaseName = "mydb";
});

// 4) 使用(构造函数注入 AppDbContext)
public class OrderService(AppDbContext db)
{
    public Task<List<Order>> AllAsync() => db.Orders.Find(_ => true).ToListAsync();
}

⚠️ 重要变更:本套件不再向 DI 注册 IMongoClient / IMongoDatabase。请通过 MongoContext 子类的 db.Client / db.Database 访问,避免多上下文歧义。


4. 连接与上下文

4.1 MongoContext(核心类型)

成员 说明
Client IMongoClient 实例
Database 连接串 / 选项中指定的数据库(默认库名 easilynet
GetCollection<T>(name) 运行时动态集合名场景(如按月分表 logs_2026_01
StartSessionAsync(startTransaction=false, ct) 开启会话/事务(需副本集或 Atlas

事务示例:

using var session = await db.StartSessionAsync(startTransaction: true);
try
{
    await db.Accounts.UpdateOneAsync(session, fromFilter, decUpdate);
    await db.Accounts.UpdateOneAsync(session, toFilter,   incUpdate);
    await session.CommitTransactionAsync();
}
catch { await session.AbortTransactionAsync(); throw; }

4.2 三种注册方式

// 方式 1:IConfiguration(推荐)—— 读取 ConnectionStrings:Mongo 或环境变量 CONNECTIONSTRINGS_MONGO
builder.Services.AddMongoContext<AppDbContext>(builder.Configuration, c => c.DatabaseName = "mydb");

// 方式 2:连接字符串
builder.Services.AddMongoContext<AppDbContext>("mongodb://localhost:27017/mydb", c => c.DatabaseName = "mydb");

// 方式 3:MongoClientSettings(高级:自定义凭据、集群配置)
builder.Services.AddMongoContext<AppDbContext>(
    new MongoClientSettings { Servers = [new("127.0.0.1", 27017)] },
    c => c.DatabaseName = "mydb");

4.3 弹性连接(Resilience

生产建议显式开启,与驱动内置自动恢复协同工作(括号为默认值):

c.Resilience.Enable = true;
c.Resilience.ServerSelectionTimeout = TimeSpan.FromSeconds(10);
c.Resilience.ConnectTimeout         = TimeSpan.FromSeconds(10);
c.Resilience.SocketTimeout          = TimeSpan.FromSeconds(60);
c.Resilience.WaitQueueTimeout       = TimeSpan.FromMinutes(1);
c.Resilience.HeartbeatInterval      = TimeSpan.FromSeconds(10);
c.Resilience.MaxConnectionPoolSize  = 100;
c.Resilience.MinConnectionPoolSize  = null;   // null = 驱动默认
c.Resilience.RetryReads  = true;
c.Resilience.RetryWrites = true;
场景 推荐
同区域低延迟 ConnectTimeout=5sServerSelectionTimeout=5s
跨区域高延迟 ConnectTimeout=20sServerSelectionTimeout=30s
高并发大流量 MaxConnectionPoolSize=200+WaitQueueTimeout=30s
单节点/代理 连接串加 directConnection=trueloadBalanced=true

弹性配置无法解决网络/认证/拓扑错误,先排查连接串。


5. 序列化与命名约定

5.1 默认约定(未调用 ConfigureMongoConventions 时自动生效)

功能 示例
驼峰字段名 PageSizepageSize
_id ↔ Id/ID 映射 _idId
枚举存字符串 Gender.Male"Male"
忽略未知字段 向前兼容
DateTime 本地化 反序列化设为 DateTimeKind.Local
decimalDecimal128 避免金额精度丢失

自定义全局约定(必须在所有 AddMongoContext 之前、最多调用一次;一旦调用则只用你声明的约定):

builder.Services.ConfigureMongoConventions(c =>
{
    c.DateTimeSerializerKind = DateTimeKind.Utc;        // 跨时区部署建议 Utc
    c.ObjectIdToStringTypes  = [typeof(SomeEntity)];    // 这些类型的 Id 存为 string($unwind 场景常用)
    c.AddConvention("default", new ConventionPack
    {
        new CamelCaseElementNameConvention(),
        new IgnoreExtraElementsConvention(true),
        new NamedIdMemberConvention("Id", "ID"),
        new EnumRepresentationConvention(BsonType.String)
    });
});

5.2 自定义序列化器(在 AddMongoContext 之后注册)

// DateOnly / TimeOnly —— 字符串可读,或 Ticks 省空间(二者同类型只能选其一)
builder.Services.RegisterSerializer(new DateOnlySerializerAsString());     // 默认 "yyyy-MM-dd"
builder.Services.RegisterSerializer(new TimeOnlySerializerAsString());     // 默认 "HH:mm:ss.ffffff"
// builder.Services.RegisterSerializer(new DateOnlySerializerAsTicks());

// 动态类型 / 匿名对象(快速验证、原型)
builder.Services.RegisterDynamicSerializer();

// System.Text.Json 类型
builder.Services.RegisterSerializer(new JsonNodeSerializer());
builder.Services.RegisterSerializer(new JsonObjectSerializer());

// 枚举键字典 Dictionary<TEnum, TValue> 等
builder.Services.RegisterGlobalEnumKeyDictionarySerializer();
  • BsonDocumentJsonConverter:在 Web API 中直接收发 BsonDocument 负载时,注册到 JsonOptions.Converters

6. 自动建索引 / 集合(启动后台服务)

所有 AddMongoXxxCreation<T>() 均注册为托管后台服务:应用启动后异步执行一次(不阻塞启动),完成即结束。

6.1 索引

public class Order
{
    public string Id { get; set; }
    [MongoIndex(EIndexType.Ascending)]                       public string UserId { get; set; }
    [MongoIndex(EIndexType.Ascending, Unique = true)]        public string OrderNo { get; set; }
    [MongoIndex(EIndexType.Descending)]                      public DateTime CreatedAt { get; set; }
    [MongoIndex(EIndexType.Ascending, ExpireAfterSeconds = 2592000)] public DateTime ExpireAt { get; set; } // TTL
    [MongoIndex(EIndexType.Ascending, Sparse = true)]        public string? ExternalId { get; set; }
}

// 复合索引(类级):遵循 ESR —— 等值→排序→范围
[MongoCompoundIndex(["userId", "createdAt"], [EIndexType.Ascending, EIndexType.Descending], Name = "idx_user_time")]
public class Order { /* ... */ }

builder.Services.AddMongoIndexCreation<AppDbContext>();

EIndexTypeAscending / Descending / Geo2D / Geo2DSphere(推荐地理) / Hashed(分片键) / Text / Multikey(数组自动) / Wildcard(动态字段)。

索引清理策略(默认安全模式,只新增/更新、不删除手工索引):

c.DropUnmanagedIndexes = true;            // ⚠️ 谨慎:会删除代码外的索引
c.ProtectedIndexPrefixes.Add("dba_");     // 即便开启清理,这些前缀也保护不删

6.2 固定大小集合(Capped)—— 日志、审计、环形缓冲

[CappedCollection("operation_logs", maxSize: 100 * 1024 * 1024)]          // 100MB 上限
[CappedCollection("audit_logs", maxSize: 50 * 1024 * 1024, MaxDocuments = 50000)] // 同时限大小+条数
public class OperationLog { /* ... */ }

builder.Services.AddMongoCappedCollectionCreation<AppDbContext>();

不支持删除单文档(只能 drop 整个集合);MaxDocuments 必须配合 MaxSize

6.3 时序集合(Time Series)—— IoT / 监控 / 行情

[TimeSeriesCollection("sensor_readings", timeField: "timestamp", metaField: "deviceId",
    granularity: TimeSeriesGranularity.Seconds, ExpireAfter = 86400 * 30)] // 可选 30 天 TTL
public class SensorReading
{
    public DateTime Timestamp { get; set; }  // timeField
    public string DeviceId { get; set; }     // metaField
    public double Temperature { get; set; }
}

builder.Services.AddMongoTimeSeriesCollectionCreation<AppDbContext>();

需 MongoDB 5.0+;timeField/metaField 创建后不可改;system.profile 为保留名。框架会自动为 (metaField, timeField) 建复合索引,并跳过时序时间字段的常规索引。


7. 查询增强:聚合、批量写入、地理空间

这些是 EasilyNET.Mongo.Core 提供的 IMongoCollection<T> 扩展,任意环境可用。

7.1 聚合扩展

// 关联查询(LEFT JOIN):$lookup + $unwind
var rows = await db.Orders.LookupAndUnwindAsync<User>(
    foreignCollectionName: "users", localField: o => o.UserId, foreignField: u => u.Id,
    asField: "user", preserveNullAndEmpty: true); // false = INNER JOIN

// 分组计数:GROUP BY status
var counts = await db.Orders.GroupByCountAsync(o => o.Status);

// 区间分布
var dist = await db.Orders.BucketAsync(o => o.Amount,
    boundaries: [0, 100, 500, 1000, 5000, (BsonValue)BsonMaxKey.Value], defaultBucket: "其它");

// 多维并行聚合(电商多面统计)
var facet = await db.Products.FacetAsync(new() { ["byBrand"] = [ /* stages */ ] });

7.2 批量写入 Fluent —— 迁移、批量导入、原子混合操作

var r = await db.Orders.BulkWriteAsync(bulk => bulk
    .InsertOne(new Order { /* ... */ })
    .UpdateOne(filter, Builders<Order>.Update.Set(o => o.Status, "shipped"))
    .UpdateMany(staleFilter, timeoutUpdate)
    .ReplaceOne(idFilter, replacement, isUpsert: true)
    .DeleteMany(Builders<Order>.Filter.Lt(o => o.CreatedAt, DateTime.UtcNow.AddYears(-3))));
// r.InsertedCount / r.ModifiedCount / r.DeletedCount

7.3 地理空间 —— 附近门店、区域筛选

public class Store
{
    [MongoIndex(EIndexType.Geo2DSphere)]   // 前提:2dsphere 索引
    public GeoJsonPoint<GeoJson2DGeographicCoordinates> Location { get; set; }
}

// 5 公里内(注意经度在前、纬度在后)
var near = GeoQueryExtensions.NearSphere<Store>(s => s.Location, 121.4737, 31.2304, maxDistanceMeters: 5000);
var list = await db.Stores.Find(near).ToListAsync();

// 含距离排序(结果附加 distance 字段,单位米)
var withDist = await db.Stores.GeoNearAsync(s => s.Location, GeoPoint.From(121.4737, 31.2304),
    new GeoNearOptions { MaxDistanceMeters = 10_000, Limit = 20, DistanceField = "distance" });

工厂:GeoPoint.From(lon, lat)GeoPolygon.From((lon,lat), ... , 闭合点)。 过滤器:NearSphere(按距离)、GeoWithin(区域内,更快但无距离)、GeoIntersects(相交)。


8. 实时数据:变更流(Change Stream)

要求:副本集或 Atlas(单节点不支持)。用于跨系统同步、事件驱动、审计追踪。

public class OrderChangeStreamHandler : MongoChangeStreamHandler<Order>
{
    public OrderChangeStreamHandler(AppDbContext db, ILogger<OrderChangeStreamHandler> logger)
        : base(db.Database, collectionName: "orders", logger, new ChangeStreamHandlerOptions
        {
            PersistResumeToken = true,                 // 重启后断点续传,不丢事件
            MaxRetryAttempts = 5,                      // 0 = 无限重试
            RetryDelay = TimeSpan.FromSeconds(2),      // 指数退避 2→4→8…
            MaxRetryDelay = TimeSpan.FromSeconds(60),
            FullDocument = ChangeStreamFullDocumentOption.UpdateLookup,
        }) { }

    protected override ChangeStreamOperationType[]? WatchOperations =>
        [ChangeStreamOperationType.Insert, ChangeStreamOperationType.Update];

    protected override Task HandleChangeAsync(ChangeStreamDocument<Order> change, CancellationToken ct)
    {
        // 处理器为 Singleton,Scoped 服务需用 IServiceScopeFactory.CreateScope()
        return Task.CompletedTask;
    }
}

builder.Services.AddMongoChangeStreamHandler<OrderChangeStreamHandler>();
选项 默认 说明
MaxRetryAttempts 5 0=无限
RetryDelay / MaxRetryDelay 2s / 60s 指数退避
PersistResumeToken false 持久化恢复令牌(重启续传)
ResumeTokenCollectionName _changeStreamResumeTokens 令牌集合名
FullDocument UpdateLookup 更新事件是否带完整文档

9. 文件存储:GridFS

适合不想引入额外对象存储、又要存大文件(>16MB)的场景。

// 默认库(fs.files / fs.chunks)
builder.Services.AddGridFSBucket<AppDbContext>();

// 自定义桶名 / 块大小
builder.Services.AddGridFSBucket<AppDbContext>(o => { o.BucketName = "uploads"; o.ChunkSizeBytes = 512 * 1024; });

// 独立库 + 键控注入(多桶)
builder.Services.AddGridFSBucket<AppDbContext>("media", "file-db", o => o.BucketName = "media");
// 注入:MediaService([FromKeyedServices("media")] IGridFSBucket bucket)

常用操作:UploadFromStreamAsync / DownloadToStreamAsync / DownloadToStreamByNameAsync / DeleteAsync / FindAsync


要求:MongoDB Atlas 或 MongoDB 8.2+ 社区版。不支持的环境会记录警告并跳过,不影响启动。

[MongoSearchIndex(Name = "product_search")]
[MongoSearchIndex(Name = "product_vector", Type = ESearchIndexType.VectorSearch)]
public class Product
{
    [SearchField(ESearchFieldType.String, IndexName = "product_search", AnalyzerName = "lucene.chinese")]
    [SearchField(ESearchFieldType.Autocomplete, IndexName = "product_search", AnalyzerName = "lucene.chinese")]
    public string Name { get; set; }

    [SearchField(ESearchFieldType.Number, IndexName = "product_search")]
    public decimal Price { get; set; }

    // AI 嵌入向量(OpenAI text-embedding-ada-002 = 1536 维)
    [VectorField(Dimensions = 1536, Similarity = EVectorSimilarity.Cosine, IndexName = "product_vector")]
    public float[] Embedding { get; set; }

    // 向量搜索预过滤字段(先缩小范围再算相似度)
    [VectorFilterField(IndexName = "product_vector")]
    public string Category { get; set; }
}

builder.Services.AddMongoSearchIndexCreation<AppDbContext>();
  • ESearchFieldTypeString/Autocomplete/Number/Date/Boolean/ObjectId/Geo/Token(精确)/Document
  • EVectorSimilarityCosine(推荐,归一化文本嵌入) / DotProduct / Euclidean
  • 实体若MongoContext 上声明集合属性,需在 [MongoSearchIndex] 上设 CollectionName,框架通过程序集扫描发现。

向量检索($vectorSearch):

var qv = await embedding.GetEmbeddingAsync("蓝牙耳机 降噪");
var pipeline = new BsonDocument[]
{
    new("$vectorSearch", new BsonDocument
    {
        { "index", "product_vector" }, { "path", "embedding" },
        { "queryVector", new BsonArray(qv.Select(f => (BsonValue)f)) },
        { "numCandidates", 150 }, { "limit", 10 },
        { "filter", new BsonDocument("category", "电子产品") }   // 预过滤
    })
};
var hits = await db.Products.Aggregate<BsonDocument>(pipeline).ToListAsync();

11. 健康检查

builder.Services.AddHealthChecks()
    .AddMongoHealthCheck<AppDbContext>(
        name: "mongodb", failureStatus: HealthStatus.Unhealthy,
        tags: ["db", "mongodb"], timeout: TimeSpan.FromSeconds(5));

app.MapHealthChecks("/health");
app.MapHealthChecks("/health/ready", new() { Predicate = c => c.Tags.Contains("db") }); // K8s 就绪
app.MapHealthChecks("/health/live",  new() { Predicate = _ => false });                  // K8s 存活

通过对数据库发送 ping 命令验证连通性。


12. 调试与可观测性(ConsoleDebug)

通过 ClientSettingsClusterConfigurator 上挂订阅器:

builder.Services.AddMongoContext<AppDbContext>(builder.Configuration, c =>
{
    c.ClientSettings = cs => cs.ClusterConfigurator = cb =>
    {
        // 开发期:命令以面板形式输出到控制台
        if (builder.Environment.IsDevelopment())
            cb.Subscribe(new ActivityEventConsoleDebugSubscriber(new() { Enable = true }));

        // 任意环境:OpenTelemetry 诊断(接 APM / Jaeger / Tempo)
        cb.Subscribe(new ActivityEventDiagnosticsSubscriber(new()
        {
            CaptureCommandText = true,     // 捕获命令文本
            ExcludeGridFSChunks = true,    // 默认开启,避免 chunks 二进制撑爆内存
            MaxCommandTextLength = 1000    // 超长截断(0 = 不限)
        }));
    };
});
  • ConsoleDebugInstrumentationOptionsEnableShouldStartCollection(按集合名过滤输出)。
  • ActivityEventDiagnosticsSubscriber 按 OpenTelemetry 语义约定打 db.systemdb.namespacedb.operation.name 等标签。
  • ⚠️ GridFS 场景务必保留 ExcludeGridFSChunks = true,否则会捕获大量二进制数据。

13. 部署前提与环境矩阵

功能 单节点 Standalone 副本集 / 分片 Atlas 备注
基本读写 / 索引 / Capped / GridFS
多文档事务 StartSessionAsync(true)
变更流
时序集合 MongoDB 5.0+
Atlas Search / Vector Search ⚠️ 仅 8.2+ ⚠️ 仅 8.2+ 社区版需 8.2+

本地开发副本集(变更流/事务)可用项目提供的 docker compose -f docker-compose.mongo.rs.yml up -d


14. 常见问题排查

现象 可能原因 / 解决
MongoConnectionPoolPausedException(连接池暂停) 网络/防火墙、认证/TLS、单节点未加 directConnection=true、副本集名不匹配、连接池耗尽。连接串显式设 serverSelectionTimeoutMSconnectTimeoutMSsocketTimeoutMS
Change stream not supported on Standalone 变更流需副本集/Atlas,启动本地副本集。
Atlas Search 索引未生成 确认 Atlas 或 8.2+;索引创建异步需等待;查日志 Failed to ensure search indexes;确认已调用 AddMongoSearchIndexCreation<T>()
注入 IMongoClient/IMongoDatabase 报找不到 已不再注册,改用 db.Client / db.Database
ConfigureMongoConventions 不生效 必须在所有 AddMongoContext 之前调用,且最多一次。
同类型 DateOnly 序列化器冲突 String 与 Ticks 同类型只能注册其一。
跨时区读到的时间不一致 ConfigureMongoConventions 中设 DateTimeSerializerKind = DateTimeKind.Utc
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed.  net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
6.26.813.102 54 8/13/2026
6.26.629.34 78 6/28/2026
Loading failed