如何在C#版MongoDB Driver中实现软删除全局过滤器?
MongoDB .NET Driver全局软删除过滤器实现方案
首先明确:MongoDB官方.NET驱动没有原生支持类似EF Core的全局查询过滤器功能,你可以通过以下几种主流方案实现需求:
方案1:统一封装仓储层(推荐)
这是目前行业内使用率最高的方案,把所有Mongo集合操作封装在仓储基类中,默认对符合软删除约定的实体追加IsActive = true过滤条件,特殊场景可通过参数关闭过滤。
// 软删除实体统一实现该接口 public interface ISoftDeletable { bool IsActive { get; } } // 仓储基类 public class BaseRepository<T> where T : class { private readonly IMongoCollection<T> _collection; public BaseRepository(IMongoDatabase database, string collectionName) { _collection = database.GetCollection<T>(collectionName); } public async Task<List<T>> GetListAsync(FilterDefinition<T> filter = null, bool includeDeleted = false, CancellationToken cancellationToken = default) { var finalFilter = filter ?? Builders<T>.Filter.Empty; // 实体实现软删除接口且不包含已删除数据时,追加过滤条件 if (!includeDeleted && typeof(ISoftDeletable).IsAssignableFrom(typeof(T))) { finalFilter &= Builders<T>.Filter.Eq(nameof(ISoftDeletable.IsActive), true); } return await _collection.Find(finalFilter).ToListAsync(cancellationToken); } // 其他查询方法(GetById、Count、Aggregate等)都按照相同逻辑追加过滤 // 封装删除方法默认走软删除,避免物理删除数据 public async Task SoftDeleteAsync(string id, CancellationToken cancellationToken = default) { if (typeof(ISoftDeletable).IsAssignableFrom(typeof(T))) { var update = Builders<T>.Update.Set(nameof(ISoftDeletable.IsActive), false); await _collection.UpdateOneAsync(Builders<T>.Filter.Eq("Id", id), update, cancellationToken: cancellationToken); } } } // 你的实体类实现软删除接口即可生效 public class User : ISoftDeletable { public string Id { get; private set; } public string Name { get; private set;} public bool IsActive{ get; private set; } }
该方案灵活度高,适配各种自定义过滤场景,也方便后续扩展其他全局规则。
方案2:基于驱动内置拦截器实现(MongoDB.Driver 2.18+版本支持)
2.18及以上版本的MongoDB .NET Driver提供了命令拦截器能力,可直接拦截所有发往数据库的查询命令,统一修改追加过滤条件,无需修改业务层代码。
public class SoftDeleteInterceptor : IMongoCommandInterceptor { // 维护支持软删除的集合列表,可根据实际业务调整 private readonly HashSet<string> _softDeleteCollections = new HashSet<string> { "Users" }; public void AfterExecution<TResult>(CommandExecutionContext<TResult> context) { // 无需处理执行后逻辑 } public void BeforeExecution(CommandExecutionContext context) { // 仅拦截查询类命令 if (context.CommandName is "find" or "aggregate") { var command = context.Command; var collectionName = context.CommandName switch { "find" => command["find"].AsString, "aggregate" => command["aggregate"].AsString, _ => string.Empty }; if (_softDeleteCollections.Contains(collectionName)) { // 追加软删除过滤条件 var softDeleteFilter = new BsonDocument("IsActive", true); if (command.Contains("filter")) { var existingFilter = command["filter"].AsBsonDocument; command["filter"] = new BsonDocument("$and", new BsonArray { existingFilter, softDeleteFilter }); } else { command["filter"] = softDeleteFilter; } } } } }
配置拦截器到客户端即可全局生效:
var clientSettings = MongoClientSettings.FromConnectionString("你的数据库连接串"); clientSettings.CommandInterceptors.Add(new SoftDeleteInterceptor()); var mongoClient = new MongoClient(clientSettings);
该方案侵入性极低,缺点是特殊场景需要绕过拦截的逻辑实现比较繁琐,会带来少量性能损耗。
方案3:扩展方法封装
如果你不想引入仓储层,可以编写IMongoCollection的扩展方法,统一封装查询逻辑,业务层约定使用扩展方法查询即可:
public static class MongoCollectionExtensions { public static IFindFluent<T, T> FindWithSoftDelete<T>(this IMongoCollection<T> collection, FilterDefinition<T> filter, bool includeDeleted = false) where T : ISoftDeletable { var finalFilter = filter; if (!includeDeleted) { finalFilter &= Builders<T>.Filter.Eq(x => x.IsActive, true); } return collection.Find(finalFilter); } }
该方案改动小、实现简单,缺点是依赖团队开发规范约束,容易出现漏用扩展方法导致过滤失效的问题。
提示:无论采用哪种方案,都建议统一封装删除操作,默认走软删除更新
IsActive为false,避免误操作物理删除数据。
内容的提问来源于stack exchange,提问作者Diana Barros
相关产品推荐
相关产品推荐

