使用Dynamic Linq查询MongoDB时的Excel OLE日期转换适配问题
问题分析与解决方案
你的思路方向是对的,但存在关键细节遗漏,同时还有更适配BsonDocument场景的简洁方案,以下是具体分析:
一、自定义类+TypeConverter思路的合理性与遗漏点
合理性
用自定义类型封装DateTime,配合TypeConverter让Dynamic Linq自动将数字转换为OLE日期的思路是可行的——Dynamic Linq在解析查询字符串中的字面量时,会尝试使用目标类型的TypeConverter做类型转换。
遗漏的关键细节
- 双向转换支持:自定义TypeConverter不仅要实现从数字(int/double)到自定义日期类型的转换,还要支持从自定义类型转换回DateTime,否则MongoDB驱动无法将其正确序列化为BSON的ISODate字段。
- 隐式/显式转换:自定义日期类需要实现与DateTime的隐式或显式转换,确保MongoDB驱动能识别并映射到ISODate。
- 特性标记:必须给自定义日期类添加
[TypeConverter(typeof(YourCustomConverter))]特性,Dynamic Linq才会触发转换器逻辑。
二、更优方案:扩展Dynamic Linq表达式解析器(适配BsonDocument场景)
如果你的项目主要使用BsonDocument而非实体类映射,自定义表达式访问器(ExpressionVisitor)是更直接的方案——无需额外定义实体类,自动将查询中的数字转换为OLE日期后再与DateTime字段比较。
实现代码
1. 自定义表达式访问器
public class OleDateConversionVisitor : ExpressionVisitor { protected override Expression VisitBinary(BinaryExpression node) { // 检查是否为DateTime与数字的比较操作 if (IsDateTimeNumberComparison(node)) { Expression dateExpr = null; ConstantExpression numberConst = null; // 区分DateTime字段和数字常量的位置 if (node.Left.Type == typeof(DateTime) && node.Right is ConstantExpression rightConst && IsNumericType(rightConst.Type)) { dateExpr = node.Left; numberConst = rightConst; } else if (node.Right.Type == typeof(DateTime) && node.Left is ConstantExpression leftConst && IsNumericType(leftConst.Type)) { dateExpr = node.Right; numberConst = leftConst; } if (dateExpr != null && numberConst != null) { // 将数字转为double后调用DateTime.FromOADate var doubleValue = Convert.ToDouble(numberConst.Value); var oaDateExpr = Expression.Call( typeof(DateTime), nameof(DateTime.FromOADate), Type.EmptyTypes, Expression.Constant(doubleValue) ); // 重建比较表达式 return Expression.MakeBinary(node.NodeType, dateExpr, oaDateExpr); } } return base.VisitBinary(node); } private bool IsDateTimeNumberComparison(BinaryExpression node) { var leftIsDateTime = node.Left.Type == typeof(DateTime); var rightIsDateTime = node.Right.Type == typeof(DateTime); var leftIsNumeric = IsNumericType(node.Left.Type); var rightIsNumeric = IsNumericType(node.Right.Type); return (leftIsDateTime && rightIsNumeric) || (rightIsDateTime && leftIsNumeric); } private bool IsNumericType(Type type) { type = Nullable.GetUnderlyingType(type) ?? type; return type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float); } }
2. 修改查询代码
MongoClientSettings settings = MongoClientSettings.FromConnectionString("mongodb://my-mongo.local"); settings.LinqProvider = LinqProvider.V2; MongoClient client = new MongoClient(settings); var database = client.GetDatabase("my_db"); var collection = database.GetCollection<BsonDocument>("colls.my_coll"); // 原查询语句 var rawQuery = collection .AsQueryable<BsonDocument>() .Where("date < 44920 and name != \"badname\" and numeric >= 520"); // 用访问器处理表达式树,转换OLE日期数字 var visitor = new OleDateConversionVisitor(); var modifiedExpr = visitor.Visit(rawQuery.Expression); var modifiedQuery = collection.AsQueryable<BsonDocument>().Provider.CreateQuery<BsonDocument>(modifiedExpr); // 执行查询 var results = modifiedQuery.ToList();
方案优势
- 无需修改原有
BsonDocument的使用方式,保持代码结构不变。 - 自动处理所有DateTime与数字的比较逻辑,不影响其他数值字段的正常比较。
- 无需额外定义实体类和转换器,代码侵入性低。
三、自定义类+TypeConverter方案的完整实现(适配实体类场景)
如果你的项目使用实体类映射MongoDB文档,可按以下方式完善你的思路:
1. 自定义日期类与转换器
[TypeConverter(typeof(OleDateTimeConverter))] public class OleDateTime { public DateTime Value { get; } public OleDateTime(DateTime dateTime) => Value = dateTime; public OleDateTime(double oaDate) => Value = DateTime.FromOADate(oaDate); public OleDateTime(int oaDate) : this((double)oaDate) { } // 隐式转换为DateTime,支持MongoDB驱动序列化 public static implicit operator DateTime(OleDateTime oleDateTime) => oleDateTime.Value; public static implicit operator OleDateTime(DateTime dateTime) => new OleDateTime(dateTime); } public class OleDateTimeConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(int) || sourceType == typeof(double) || base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is int intValue) return new OleDateTime(intValue); if (value is double doubleValue) return new OleDateTime(doubleValue); return base.ConvertFrom(context, culture, value); } }
2. 定义实体类与查询
public class MyDocument { public ObjectId Id { get; set; } public string Name { get; set; } public double Numeric { get; set; } public OleDateTime Date { get; set; } } // 查询代码 var collection = database.GetCollection<MyDocument>("colls.my_coll"); var results = collection .AsQueryable() .Where("Date < 44920 and Name != \"badname\" and Numeric >= 520") .ToList();
内容的提问来源于stack exchange,提问作者andrejr
相关产品推荐
相关产品推荐

