MongoDB C#驱动泛型方法中Lambda访问属性的实现问题
这个问题我之前也踩过坑——泛型里直接用字符串字段名或者反射拼接的Lambda总是出问题,核心原因在于MongoDB C#驱动需要解析成员访问表达式树来获取字段的序列化元数据,而反射调用或者字符串如果没对齐存储字段名就会失效。下面是几种可靠的解决方案:
1. 手动构建强类型属性访问表达式树
这是最推荐的方式,驱动能完美解析表达式里的属性访问,自动处理[BsonElement]这类映射特性,还能保持强类型安全。
先写一个辅助方法来生成属性访问的Lambda表达式:
private static Expression<Func<TDocument, TField>> CreatePropertyAccessor<TDocument, TField>(string propertyName) { var param = Expression.Parameter(typeof(TDocument), "x"); var property = Expression.Property(param, propertyName); return Expression.Lambda<Func<TDocument, TField>>(property, param); }
然后在你的泛型方法里直接用这个表达式构建过滤器:
public FilterDefinition<TDocument> CreateEqualityFilter<TDocument, TField>(string propertyName, TField value) { var propertyExpr = CreatePropertyAccessor<TDocument, TField>(propertyName); return Builders<TDocument>.Filter.Eq(propertyExpr, value); }
为什么之前的反射Lambda不行?
你尝试的x => x.GetType().GetProperty("UserName")本质是一个方法调用表达式(MethodCallExpression),而MongoDB驱动需要的是成员访问表达式(MemberExpression)——它没法从反射调用里提取字段的序列化信息,所以才会抛出Unable to determine the serialization information的异常。
2. 字符串字段名的正确用法(如果一定要用)
如果你坚持用字符串方式,要注意:字符串必须匹配MongoDB存储的实际字段名,而不是C#类的属性名。比如你的类属性加了[BsonElement("user_name")],那字符串就得写"user_name"而不是"UserName"。
泛型里用字符串失效的大概率原因是:泛型类型的属性有自定义Bson映射,而你用了C#属性名而非存储字段名,而具体类型测试时刚好属性名和存储字段名一致,所以能正常工作。
如果要让字符串方式在泛型里可靠,可以结合BsonClassMap来获取实际存储的字段名:
public FilterDefinition<TDocument> CreateEqualityFilter<TDocument>(string propertyName, object value) { var classMap = BsonClassMap.LookupClassMap(typeof(TDocument)); var memberMap = classMap.GetMemberMap(propertyName); var fieldName = memberMap.ElementName; // 获取实际存储的字段名 return Builders<TDocument>.Filter.Eq(fieldName, value); }
完整泛型仓库示例
把上面的逻辑整合到泛型仓库里,使用起来就和强类型写法一样方便:
public class MongoGenericRepository<TDocument> { private readonly IMongoCollection<TDocument> _collection; public MongoGenericRepository(IMongoDatabase database, string collectionName) { _collection = database.GetCollection<TDocument>(collectionName); } private static Expression<Func<TDocument, TField>> CreatePropertyAccessor<TDocument, TField>(string propertyName) { var param = Expression.Parameter(typeof(TDocument), "x"); var property = Expression.Property(param, propertyName); return Expression.Lambda<Func<TDocument, TField>>(property, param); } public async Task<TDocument> FindByPropertyAsync<TField>(string propertyName, TField value) { var filter = Builders<TDocument>.Filter.Eq(CreatePropertyAccessor<TDocument, TField>(propertyName), value); return await _collection.Find(filter).FirstOrDefaultAsync(); } public async Task<List<TDocument>> FindManyByPropertyAsync<TField>(string propertyName, TField value) { var filter = Builders<TDocument>.Filter.Eq(CreatePropertyAccessor<TDocument, TField>(propertyName), value); return await _collection.Find(filter).ToListAsync(); } }
这样不管你的泛型类型有没有自定义Bson映射,都能正确生成过滤器,完全等效于直接写x => x.PropertyName的强类型写法。
内容的提问来源于stack exchange,提问作者Aousaf Rashid

