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

如何为非泛型IQueryable创建适配泛型ProviderAdapter的Map扩展方法?

这个方案完全可行,不过因为涉及到非泛型IQueryable的类型动态解析,确实需要借助反射来搞定泛型实例化和委托/表达式的转换。我给你分两种常用场景来具体实现:

场景1:使用Func<dynamic, TModel>作为映射委托

如果你的ProviderAdapter是在内存中处理对象映射(不需要将表达式翻译成SQL之类的查询语句),可以用这种方式,实现起来更简单:

public static class QueryableExtensions
{
    public static IQueryable<TModel> Map<TModel>(this IQueryable query, Func<dynamic, TModel> mapping)
    {
        if (query == null) throw new ArgumentNullException(nameof(query));
        if (mapping == null) throw new ArgumentNullException(nameof(mapping));

        // 获取非泛型IQueryable的元素类型,也就是对应的TDto实际类型
        Type dtoType = query.ElementType;
        
        // 构造ProviderAdapter的泛型类型:ProviderAdapter<dtoType, TModel>
        Type providerAdapterGenericType = typeof(ProviderAdapter<,>).MakeGenericType(dtoType, typeof(TModel));
        
        // 将Func<dynamic, TModel>转换为Func<dtoType, TModel>,适配ProviderAdapter的构造函数参数
        var targetDelegateType = typeof(Func<,>).MakeGenericType(dtoType, typeof(TModel));
        Func<object, TModel> wrapper = obj => mapping((dynamic)obj);
        var convertedMapping = Delegate.CreateDelegate(targetDelegateType, wrapper.Target, wrapper.Method);
        
        // 反射实例化ProviderAdapter
        var providerAdapter = Activator.CreateInstance(providerAdapterGenericType, query, convertedMapping);
        
        // 最后实例化QueryableAdapter并返回(和泛版Map方法逻辑一致)
        Type queryableAdapterGenericType = typeof(QueryableAdapter<>).MakeGenericType(typeof(TModel));
        return (IQueryable<TModel>)Activator.CreateInstance(queryableAdapterGenericType, providerAdapter);
    }
}

这里的核心是用一个包装委托把object转成dynamic传给原映射方法,再通过Delegate.CreateDelegate把包装委托转换成目标类型的委托,完美匹配ProviderAdapter构造函数的参数要求。

场景2:使用Expression<Func<dynamic, TModel>>支持查询翻译

如果你的ProviderAdapter需要处理表达式树(比如配合EF Core将映射逻辑翻译成SQL),那换成表达式类型的参数会更合适,避免客户端求值的问题:

public static class QueryableExtensions
{
    public static IQueryable<TModel> Map<TModel>(this IQueryable query, Expression<Func<dynamic, TModel>> mapping)
    {
        if (query == null) throw new ArgumentNullException(nameof(query));
        if (mapping == null) throw new ArgumentNullException(nameof(mapping));

        Type dtoType = query.ElementType;
        var targetFuncType = typeof(Func<,>).MakeGenericType(dtoType, typeof(TModel));
        
        // 替换原表达式中的dynamic参数为实际的dtoType参数,并处理dynamic成员访问
        var newParameter = Expression.Parameter(dtoType, "source");
        var expressionReplacer = new DynamicExpressionReplacer(newParameter);
        var modifiedBody = expressionReplacer.Visit(mapping.Body);
        var targetExpression = Expression.Lambda(targetFuncType, modifiedBody, newParameter);
        
        // 反射创建ProviderAdapter实例
        var providerAdapterGenericType = typeof(ProviderAdapter<,>).MakeGenericType(dtoType, typeof(TModel));
        var providerAdapter = Activator.CreateInstance(providerAdapterGenericType, query, targetExpression);
        
        // 实例化并返回QueryableAdapter
        var queryableAdapterGenericType = typeof(QueryableAdapter<>).MakeGenericType(typeof(TModel));
        return (IQueryable<TModel>)Activator.CreateInstance(queryableAdapterGenericType, providerAdapter);
    }

    // 自定义表达式访问器,替换dynamic相关的表达式节点
    private class DynamicExpressionReplacer : ExpressionVisitor
    {
        private readonly ParameterExpression _replacementParameter;

        public DynamicExpressionReplacer(ParameterExpression replacementParameter)
        {
            _replacementParameter = replacementParameter;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            // 把原表达式中的dynamic参数替换成实际的dtoType参数
            return node.Type == typeof(dynamic) ? _replacementParameter : base.VisitParameter(node);
        }

        protected override Expression VisitDynamic(DynamicExpression node)
        {
            // 把dynamic的成员访问转换成对应类型的成员访问,确保表达式能被ORM解析
            var visitedArgs = Visit(node.Arguments);
            return Expression.MakeMemberAccess(visitedArgs[0], node.Member);
        }
    }
}

这个方案的关键是用自定义的ExpressionVisitor把原表达式中dynamic相关的节点替换成实际类型的节点,这样表达式树就能被正常解析和转换,不会出现无法翻译成SQL的问题。

总结一下:两种方案都能满足需求,如果你只需要内存映射,选第一种更简洁;如果要支持查询翻译,第二种是更优的选择。

内容的提问来源于stack exchange,提问作者Jarek Mazur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 03:59:30