如何为非泛型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
相关产品推荐
相关产品推荐

