EasilyNET.Mongo.AspNetCore
6.26.313.141
dotnet add package EasilyNET.Mongo.AspNetCore --version 6.26.313.141
NuGet\Install-Package EasilyNET.Mongo.AspNetCore -Version 6.26.313.141
<PackageReference Include="EasilyNET.Mongo.AspNetCore" Version="6.26.313.141" />
<PackageVersion Include="EasilyNET.Mongo.AspNetCore" Version="6.26.313.141" />
<PackageReference Include="EasilyNET.Mongo.AspNetCore" />
paket add EasilyNET.Mongo.AspNetCore --version 6.26.313.141
#r "nuget: EasilyNET.Mongo.AspNetCore, 6.26.313.141"
#:package EasilyNET.Mongo.AspNetCore@6.26.313.141
#addin nuget:?package=EasilyNET.Mongo.AspNetCore&version=6.26.313.141
#tool nuget:?package=EasilyNET.Mongo.AspNetCore&version=6.26.313.141
EasilyNET.Mongo.AspNetCore
为 ASP.NET Core 应用提供完整的 MongoDB 集成方案,涵盖连接注册、序列化、自动索引(含 Atlas Search / Vector Search)、变更流、GridFS 文件存储、固定集合/时序集合自动创建、健康检查等开箱即用能力。
目录
- 安装
- ⚠️ 中断性变更(Breaking Changes)
- 快速开始:注册 MongoContext
- 字段映射与命名约定
- 自定义序列化器
- 自动创建索引
- 自动创建时序集合
- 自动创建固定大小集合
- Atlas Search / Vector Search 索引自动创建
- 变更流(Change Stream)
- GridFS 文件存储
- 健康检查
- 使用 EasilyNET.AutoDependencyInjection 集成
- 常见问题排查
安装
dotnet add package EasilyNET.Mongo.AspNetCore
⚠️ 中断性变更(Breaking Changes)
不再注册 IMongoClient 和 IMongoDatabase 到 DI 容器
此前版本会将 IMongoClient 和 IMongoDatabase 注册为单例服务,可直接通过 DI 注入。当前版本已移除此行为。
请通过 MongoContext 子类实例的 Client 和 Database 属性访问:
// ❌ 旧方式(不再支持)
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=true 或 loadBalanced=true |
| Atlas / 云托管 | 保持默认值,确保 RetryReads=true, RetryWrites=true |
⚠️ 弹性配置不能解决网络/认证/拓扑错误,请先排查连接串配置。
字段映射与命名约定
默认情况下(未调用 ConfigureMongoConventions),框架会自动注册以下规则:
| 功能 | 说明 | 示例 |
|---|---|---|
| 驼峰字段名 | C# PascalCase → MongoDB camelCase |
PageSize → pageSize |
_id 映射 |
自动将 _id 与实体中的 Id / ID 属性互相映射 |
_id ↔ Id |
| 枚举存字符串 | 枚举值以字符串形式存储,便于阅读 | 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 文档),然后在应用启动时调用:
var app = builder.Build();
// 自动扫描所有带索引特性的实体,后台创建/更新索引(不阻塞应用启动)
app.UseCreateMongoIndexes<MyDbContext>();
框架会:
- 扫描
MyDbContext的所有IMongoCollection<T>属性 - 比对数据库中的现有索引与代码声明
- 创建缺失的索引;对于结构变更的索引,优先尝试“先建后删”,冲突时回退为“删后建”
索引管理策略
默认情况下,框架只创建/更新代码中声明的索引,不会删除数据库中手动创建的索引(安全模式)。如需自动清理未在代码中声明的索引:
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; }
}
在 Program.cs 中启用自动创建:
// 若集合不存在则自动创建,已存在则跳过
app.UseCreateMongoTimeSeriesCollection<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
{
// ...
}
app.UseCreateMongoCappedCollections<MyDbContext>();
⚠️ Capped 集合不支持删除单个文档(只能
drop整个集合)。
Atlas Search / Vector Search 索引自动创建
Atlas Search 是基于 Apache Lucene 的全文搜索引擎,支持中文分词、相关性排序、自动补全。Vector Search 是 AI 语义搜索的核心能力,广泛用于 RAG(检索增强生成)场景。
通过 [MongoSearchIndex]、[SearchField]、[VectorField]、[VectorFilterField] 特性声明(详见 EasilyNET.Mongo.Core 文档):
[MongoSearchIndex(Name = "product_search")]
[MongoSearchIndex(Name = "product_vector", Type = ESearchIndexType.VectorSearch)]
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)
[VectorField(Dimensions = 1536, Similarity = EVectorSimilarity.Cosine,
IndexName = "product_vector")]
public float[] Embedding { get; set; }
// 向量搜索的预过滤字段(只在指定分类中搜索)
[VectorFilterField(IndexName = "product_vector")]
public string Category { get; set; }
}
对于未在 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,集合名称会自动解析。
在启动时调用:
// 方式 1(推荐):在服务注册阶段添加后台服务,自动创建索引
builder.Services.AddMongoSearchIndexCreation<MyDbContext>();
// 方式 2:在中间件管道中调用(异步后台创建,不阻塞应用启动)
app.UseCreateMongoSearchIndexes<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.AspNetCore.ChangeStreams;
using EasilyNET.Mongo.AspNetCore.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 |
ResumeTokenCollectionName |
"_changeStreamResumeTokens" |
存储恢复令牌的集合名 |
FullDocument |
UpdateLookup |
更新事件是否返回完整文档 |
断点续传工作原理
开启 PersistResumeToken = true 后:
事件 1 → HandleChangeAsync() → 保存 Token-A
事件 2 → HandleChangeAsync() → 保存 Token-B
[应用重启]
从 Token-B 恢复 → 继续处理事件 3、4、5 ...(无遗漏,无重复)
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.AspNetCore.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();
}
public override async Task ApplicationInitialization(ApplicationContext context)
{
var app = context.GetApplicationHost() as IApplicationBuilder;
app?.UseCreateMongoIndexes<DbContext>();
app?.UseCreateMongoIndexes<DbContext2>();
await base.ApplicationInitialization(context);
}
}
创建根模块
[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 错误 | 检查用户名、密码、authSource、tls 参数 |
| 单节点 / 代理访问 | 连接串加 directConnection=true 或 loadBalanced=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 索引未创建
- 确认使用的是 MongoDB Atlas 或 MongoDB 8.2+ 社区版
- Atlas 侧索引创建是异步的,通常需要几秒到几分钟
- 检查日志中是否有
Failed to ensure search indexes错误 - 确认已在服务注册阶段调用
builder.Services.AddMongoSearchIndexCreation<MyDbContext>()(推荐) 或使用兼容方式app.UseCreateMongoSearchIndexes<MyDbContext>()
| Product | Versions 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. |
-
net10.0
- EasilyNET.Core (>= 6.26.313.141)
- EasilyNET.Mongo.Core (>= 6.26.313.141)
-
net11.0
- EasilyNET.Core (>= 6.26.313.141)
- EasilyNET.Mongo.Core (>= 6.26.313.141)
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.313.141 | 32 | 3/13/2026 |
| 6.26.227.92 | 91 | 2/27/2026 |
| 6.26.201.257 | 106 | 1/31/2026 |
| 6.26.114.102 | 126 | 1/14/2026 |
| 6.26.107.173 | 118 | 1/7/2026 |
| 5.25.1212.131 | 165 | 12/12/2025 |
| 5.25.1112.15 | 323 | 11/11/2025 |
| 4.25.1016.112 | 335 | 10/16/2025 |