以更好的性能将 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 个不同的实体,Entity1
和 Entity2
,具有完全相同的属性。它们是从数据库中的不同视图自动生成的,每个视图都有自己的实体类型。
我通过以下方式查询这些实体:
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<>
方法来自:https://***.com/a/8819149
我也尝试关注@DavidG 评论:
Mapper.CreateMap<Entity2Type, Entity1Type>
return _myRepository2.GetQueryable().ProjectTo<Entity1>();
但它失败并出现此错误:
无法在 LINQ to Entities 查询中构造实体或复杂类型“Entity1Type”。”
如何只创建一个端点,从_myRepository1
或_myRepository2
返回可查询的数据并具有良好的性能?
【问题讨论】:
你不能投,但你应该可以选择:_myRepository2.GetQueryable().Select(c => new Entity1 ... copy all properties here ... )
它可能会起作用,但由于我有很多属性,有没有其他方法可以自动完成而不是一一选择?
如果您不想写出所有属性,您可以使用 Automapper 并使用 ProjectTo
feature。
@DavidG 查看我的编辑,我尝试了您的解决方案,但它在执行时一直失败
【参考方案1】:
我终于找到了解决方案,但我不确定它是否最优雅:
我已经像这样更改了我的Mapper.Map<>
:
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<EntityB>
,所以我的用户可以使用 OData 过滤器。感谢ApplyTo<>
(所以在请求执行之前)应用它们,它避免了将整个表加载到内存中,而只返回获取的结果。
【讨论】:
【参考方案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 以实现 IAsyncEnumerable [重复]