以更好的性能将 IQueryable<X> 转换为 IQueryable<Y>

Posted

技术标签:

【中文标题】以更好的性能将 IQueryable<X> 转换为 IQueryable<Y>【英文标题】:Cast IQueryable<X> to IQueryable<Y> with better performance 【发布时间】:2021-12-24 19:21:17 【问题描述】:

我有 2 个不同的实体,Entity1Entity2,具有完全相同的属性。它们是从数据库中的不同视图自动生成的,每个视图都有自己的实体类型。

我通过以下方式查询这些实体:

protected generatedRepository<Entity1Type> _myRepository1;
_myRepository1.GetQueryable();

protected generatedRepository<Entity2Type> _myRepository2;
_myRepository2.GetQueryable();

我正在使用 OData 创建 API 端点,我必须返回一个 IQueryable<...> 以让用户将 OData 过滤器应用于其请求

当我想从Entity1 返回实体时,我只需要写:

public IQueryable<Entity1Type> Get()

    return _myRepository.GetQueryable();

这个新端点可以从 /api/ControllerName?$ODataFilter=...

但是,我想从_myRepository1_myRepository2 返回数据条件使用完全相同的端点

如果我使用相同的签名,Entity2Type 必须强制转换为 Entity1Type 才能返回

我试过了

return _myRepository2.GetQueryable().Cast<Entity1Type>();  

但它失败了:

无法将类型“MyEntities2”转换为类型“MyEntities1”。 LINQ to Entities 仅支持转换 EDM 基元或枚举类型。

我也试过了:

return _myRepository2.GetQueryable().ToDTO<Entity2, Entity1>();

它可以工作,但是视图有超过 1M 行并且它会加载所有行,这是不可接受的 ToDto&lt;&gt; 方法来自:https://***.com/a/8819149

我也尝试关注@DavidG 评论:

Mapper.CreateMap<Entity2Type, Entity1Type>

return _myRepository2.GetQueryable().ProjectTo<Entity1>();

但它失败并出现此错误:

无法在 LINQ to Entities 查询中构造实体或复杂类型“Entity1Type”。”

如何只创建一个端点,从_myRepository1_myRepository2 返回可查询的数据并具有良好的性能?

【问题讨论】:

你不能投,但你应该可以选择:_myRepository2.GetQueryable().Select(c =&gt; new Entity1 ... copy all properties here ... ) 它可能会起作用,但由于我有很多属性,有没有其他方法可以自动完成而不是一一选择? 如果您不想写出所有属性,您可以使用 Automapper 并使用 ProjectTo feature。 @DavidG 查看我的编辑,我尝试了您的解决方案,但它在执行时一直失败 【参考方案1】:

我终于找到了解决方案,但我不确定它是否最优雅:

我已经像这样更改了我的Mapper.Map&lt;&gt;

public IQueryable<Entity2> Get(ODataQueryOptions opts)

    Mapper.CreateMap<Entity2Type, Entity2Type>() //Yes, Entity2 to Entity2, no typo
        .ForMember(d => d.Prop1, option => option.MapFrom(src => someCondition ? src.Prop1 : src.Prop2))
        .ForMember(d => d.Prop3, option => option.MapFrom(src => someCondition ? src.Prop3 : src.Prop4))
        .IgnoreAllNonExisting();

    var query = opts.ApplyTo(_myRepository2.GetQueryable()) as IQueryable<Entity2>;
    var results = Mapper.Map<IList<Entity2>>(query.ToList());

    return results.AsQueryable();

它返回一个IQueryable&lt;EntityB&gt;,所以我的用户可以使用 OData 过滤器。感谢ApplyTo&lt;&gt;(所以在请求执行之前)应用它们,它避免了将整个表加载到内存中,而只返回获取的结果。

【讨论】:

【参考方案2】:

您可以尝试使用与您的 2 个实体匹配的通用 DTO 并编写正确的投影。

return _myRepository2.GetQueryable().Select(e2 => 'write your projection from e2 to dto')

entity1 也一样:

return _myRepository1.GetQueryable().Select(e1 => 'write your projection from e1 to dto')

还关于另一篇文章中公开的 ToDTO

【讨论】:

因为我有50多个属性,将来可能还会更多,我真的应该将所有属性一一投影吗?有什么方法可以自动完成(例如根据他们的名字)? 您可以使用 ToDto 删除 ToList()。或者你可以考虑使用像 automapper 这样的映射工具来完成这项工作 您不能在此处使用该 ToDto,因为即使没有 ToList,它也会实现源集合(通过对其执行 foreach)。

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

使用 forEach 迭代 IQueryable 与调用 ToList() 然后执行 forEach - 性能

如何在 ASP Net Core Web API 上正确地将列表转换为 IQueryable

将 IQueryable<int> 转换为 <int>

转换 IQueryable 以实现 IAsyncEnumerable [重复]

IQueryable和IEnumerable,IList的区别

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