You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Dynamic Linq查询MongoDB时的Excel OLE日期转换适配问题

问题分析与解决方案

你的思路方向是对的,但存在关键细节遗漏,同时还有更适配BsonDocument场景的简洁方案,以下是具体分析:


一、自定义类+TypeConverter思路的合理性与遗漏点

合理性

用自定义类型封装DateTime,配合TypeConverter让Dynamic Linq自动将数字转换为OLE日期的思路是可行的——Dynamic Linq在解析查询字符串中的字面量时,会尝试使用目标类型的TypeConverter做类型转换。

遗漏的关键细节

  1. 双向转换支持:自定义TypeConverter不仅要实现从数字(int/double)到自定义日期类型的转换,还要支持从自定义类型转换回DateTime,否则MongoDB驱动无法将其正确序列化为BSON的ISODate字段。
  2. 隐式/显式转换:自定义日期类需要实现与DateTime的隐式或显式转换,确保MongoDB驱动能识别并映射到ISODate。
  3. 特性标记:必须给自定义日期类添加[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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 09:00:40