将 IQueryable<EntityObject> 转换为 IQueryable<Specific>
Posted
技术标签:
【中文标题】将 IQueryable<EntityObject> 转换为 IQueryable<Specific>【英文标题】:Cast IQueryable<EntityObject> to IQueryable<Specific> 【发布时间】:2012-02-07 18:56:32 【问题描述】:我们正在尝试将IQueryable<EntityObject>
的实例转换为IQueryable<SpecificEntityObject>
,SpecificEntityObject
类型仅在运行时已知。
我们尝试使用下面的代码,因为类型或命名空间“objType”不存在,所以无法编译。
var t = query.ElementType;
Type objType = typeof(IQueryable<>).MakeGenericType(t);
var typed = query.Cast<IEnumerable<objType>>();
var grouped = typed.GroupByMany(groupBy.Select(grp => grp.Expression).ToArray());
有什么想法吗?
【问题讨论】:
基本上,你不能方便地这样做 - 你必须使用反射,然后typed
将是object
或非泛型@ 987654327@,其他的都不起作用。你可以在这里做一些事情,但几乎所有事情都会像任何东西一样丑陋......这里没有灵丹妙药。附带说明一下,Cast
调用将是 <t>
,而不是 <IQueryable<t>>
(如果你能原谅那里尴尬的伪语法)
您到底想做什么:将查询结果转换为您仅在运行时知道的类型,或者扩展查询以将转换操作表达为您仅在运行时知道的特定类型?
为什么你想这样做?为什么你说你只在运行时知道类型,然后像在编译时知道的那样使用它?
我们正在尝试为我们的应用程序创建一个通用的报告 API。因此,在应用程序开发的所有阶段,我们永远不会知道具体的类型。我们有各种表,但是我们将所有这些都存储在 list使用以下 IQueryable 扩展泛型方法query.ToDTO<sourceType,DestType>();
:
public static class QueryableExtensions
public static IQueryable<TDest> ToDTO<TSource, TDest>(this IQueryable<TSource> source)
List<TDest> destinationList = new List<TDest>();
List<TSource> sourceList = source.ToList<TSource>();
var sourceType = typeof(TSource);
var destType = typeof(TDest);
foreach (TSource sourceElement in sourceList)
TDest destElement = Activator.CreateInstance<TDest>();
//Get all properties from the object
PropertyInfo[] sourceProperties = typeof(TSource).GetProperties();
foreach (PropertyInfo sourceProperty in sourceProperties)
//and assign value to each propery according to property name.
PropertyInfo destProperty = destType.GetProperty(sourceProperty.Name);
destProperty.SetValue(destElement, sourceProperty.GetValue(sourceElement, null), null);
destinationList.Add(destElement);
return destinationList.AsQueryable();
【讨论】:
不错的代码,我在我的项目中使用它。一点小提示:destinationList.Add(destElement);应该不在括号内。 奇怪的是 OP 接受了这个作为答案。她不仅在编译时没有TDest
,而且这种方法会急切地将所有内容加载到内存中,这会吸收大量数据。【参考方案2】:
对于其他想要从 db 查询中投射非 db 值的人,u/Luis Aguilar 中的 this 项目对我非常非常有帮助。
我有一个非常大的旧数据库 (450GB),需要提供给 OData/WebAPI。
OData 要求意味着在将源数据返回给用户之前,我无法过滤(很多)源数据。我们可以将其孤立起来,但除此之外,他们的数据可以随意查询。
然而,更重要的是,遗留数据过于复杂而无法按原样公开,并且需要大量的业务逻辑来整理必要的数据(导航属性/外键的Include
,冗长的子句谓词等) .
这意味着分页和结果限制在查询已经实现之后才可用。
这类事情的正常快捷方式涉及涉及物化/急切加载的各种策略。但是,由于数据集的大小和缺乏过滤,这将导致大量进程内存膨胀和内存不足崩溃。
所以,一些代码。这是我的配置调用,类似于 AutoMapper 或 OData 所需的:
using ExpressionFramework.Projections;
using ExpressionFramework.Projections.Configuration;
public class ProjectionModelBuilder : ProjectionModel
protected override void OnModelCreating(ProjectionModelBuilder modelBuilder)
ClientDTO.ProjectionModel(modelBuilder);
OrderDTO.ProjectionModel(modelBuilder);
AnotherDTO.ProjectionModel(modelBuilder);
这种设计允许我将 DTO 类中的投影规则与其余的业务逻辑保持一致。下面是 DTO 级代码的样子:
public static void ProjectionModel(ProjectionModelBuilder modelBuilder)
modelBuilder
.Projection<ClientDTO>()
.ForSource<Client>(configuration =>
configuration.Property(dto => dto.Name).ExtractFrom(entity => entity.Name);
// etc
);
Client
是我的实体/EDM 类型,映射到数据库表和大量外键。
然后获得翻译/投影的Queryable
,就是这样:
IClientQueryService service = _ioc.Resolve<IClientQueryService>(); // Repository pattern
var q = service.GetClients(); // withManyNavigationIncludes
var r = q.Where<Item>(
i =>
i.Name != null
&& i.Name != ""
// lather rinse repeat, with many sub-objects navigated also
).AsQueryable();
var projectionModel = new ProjectionModelBuilder();
var s = projectionModel.Project<ClientDTO, Client>(r).AsQueryable();
只有最后两行是相关的,其余的都包含在上下文中。
我要做的最后一件事是在 Luis 代码中的 ProjectionSourceTypeConfiguration.cs
的构造函数中设置 this.IsAutoConfigured = false;
;这允许我手动订购我的投影定义,以便父类中的导航属性可以成功配置它们的投影。
我对@987654323@ 的工作表示感谢。在编写了我自己的 LINQ Provider/ExpressionVisitor
并使用各种泛型方法调用、翻译和 treewalks 后仍然存在各种问题,他的项目真是天赐之物。
如果您确实发现必须出于性能或其他原因使用自己的表达式处理,我建议您从these two 开始回答。
【讨论】:
【参考方案3】:如果您没有编译时类型信息,则必须一直依赖丑陋的反射代码。 dynamic
关键字可能会使事情变得有点整洁。尝试类似:
var typed = (IQueryable)typeof(Queryable)
.GetMethod(nameof(Queryable.Cast))
.MakeGenericMethod(typeof(SpecificEntityObject)) // <--- your runtime type here
.Invoke(null, new object[] query );
// more reflection based calls to follow for further LINQ operations.
还有一个不错的小扩展方法:
public static IQueryable Cast(this IQueryable source, Type type)
在System.Linq.Dynamic.Core
库中。
【讨论】:
【参考方案4】:var t = query.ElementType;
Type objType = typeof(IQueryable<>).MakeGenericType(t);
var typed = query.Cast<object>();
var grouped = typed.GroupByMany(groupBy.Select(grp => grp.Expression).ToArray());
【讨论】:
嗨,欢迎来到 SO。你能解释一下你的解决方案吗?【参考方案5】:如果您开始使用反射,则还需要将其与所有方法一起使用。所以你需要创建
var myEnumType = typeof(IEnumerable<>).MakeGenericType(objType);
并且还在运行时找到扩展方法Cast匹配所需的类型。
typeof(Enumerable).GetMethod("Cast", BindingFlags.Public |
BindingFlags.Static,
null,
CallingConventions.Any,
new Type[] typeof(object),
null);
那么你就可以调用那个方法了
【讨论】:
myEnumType.GetMethod
在这里不起作用,因为 Cast 不是 IEnumerable<>
类型的静态/实例方法。你需要typeof(Enumerable).GetMethod
以上是关于将 IQueryable<EntityObject> 转换为 IQueryable<Specific>的主要内容,如果未能解决你的问题,请参考以下文章
无法将类型“System.Linq.IQueryable<int>”隐式转换为“int?”
如何将 ODataQueryOptions<T> 应用于 IQueryable<X>
如何将 Kendo UI Grid 与 ToDataSourceResult()、IQueryable<T>、ViewModel 和 AutoMapper 一起使用?
有没有办法让 Swashbuckle 将 OData 参数添加到 Web API 2 IQueryable<T> 端点?