将 IQueryable<EntityObject> 转换为 IQueryable<Specific>

Posted

技术标签:

【中文标题】将 IQueryable<EntityObject> 转换为 IQueryable<Specific>【英文标题】:Cast IQueryable<EntityObject> to IQueryable<Specific> 【发布时间】:2012-02-07 18:56:32 【问题描述】:

我们正在尝试将IQueryable&lt;EntityObject&gt; 的实例转换为IQueryable&lt;SpecificEntityObject&gt;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 调用将是 &lt;t&gt;,而不是 &lt;IQueryable&lt;t&gt;&gt;(如果你能原谅那里尴尬的伪语法) 您到底想做什么:将查询结果转换为您仅在运行时知道的类型,或者扩展查询以将转换操作表达为您仅在运行时知道的特定类型? 为什么你想这样做?为什么你说你只在运行时知道类型,然后像在编译时知道的那样使用它? 我们正在尝试为我们的应用程序创建一个通用的报告 API。因此,在应用程序开发的所有阶段,我们永远不会知道具体的类型。我们有各种表,但是我们将所有这些都存储在 list 中,所以当我们尝试使用 GroupBy 的东西时,我们的 GroupBy 正在反映 EntityObject 并发现它没有我们需要的属性,因为基本类型是 EntityObject 而不是一个特定的类型 - 我们现在觉得我们的设计可能是错误的......关于如何动态 GroupBy 的任何想法? - 这有意义吗? 我们以另一种方式对所有这些进行了排序...我们重新组织了查询的排列方式...现在我们(使用 Dynamic Linq)创建了一个新的组键并将其作为解析的字符串传递在 - 谢谢你的帮助! 【参考方案1】:

使用以下 IQueryable 扩展泛型方法query.ToDTO&lt;sourceType,DestType&gt;();

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;;这允许我手动订购我的投影定义,以便父类中的导航属性可以成功配置它们的投影。

我对@9​​87654323@ 的工作表示感谢。在编写了我自己的 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&lt;&gt; 类型的静态/实例方法。你需要typeof(Enumerable).GetMethod

以上是关于将 IQueryable<EntityObject> 转换为 IQueryable<Specific>的主要内容,如果未能解决你的问题,请参考以下文章

将 IQueryable<int> 转换为 <int>

无法将类型“System.Linq.IQueryable<int>”隐式转换为“int?”

如何将 ODataQueryOptions<T> 应用于 IQueryable<X>

如何将 Kendo UI Grid 与 ToDataSourceResult()、IQueryable<T>、ViewModel 和 AutoMapper 一起使用?

IQueryable<decimal> 转十进制

有没有办法让 Swashbuckle 将 OData 参数添加到 Web API 2 IQueryable<T> 端点?