Search code examples
c#performancecastingiqueryable

Cast IQueryable<X> to IQueryable<Y> with better performance


I have 2 different entities, Entity1 and Entity2, with the very same properties. They are auto-generated from different views from DB and each view has its own Entity type.

I query these entities via :

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

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

I'm creating an API endpoint using OData, and I must return a IQueryable<...> to let the user apply OData filters to its request

When I want to return entities from Entity1, I just have to write :

public IQueryable<Entity1Type> Get()
{
    return _myRepository.GetQueryable();
}

and this new endpoint is accessible from /api/ControllerName?$ODataFilter=...

However, I'd like to return data conditionally from _myRepository1 or _myRepository2 using the very same endpoint

If I use the same signature, Entity2Type must be cast to Entity1Type to be returned

I tried

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

But it fails :

Unable to cast the type 'MyEntities2' to type 'MyEntities1'. LINQ to Entities only supports casting EDM primitive or enumeration types.

I also tried :

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

It works, but the views have more than 1M rows and it loads all rows, which is not acceptable

The ToDto<> method came from : https://stackoverflow.com/a/8819149

I also tried following @DavidG comment :

Mapper.CreateMap<Entity2Type, Entity1Type>

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

But it fails with this error :

The entity or complex type 'Entity1Type' cannot be constructed in a LINQ to Entities query."

How can I create only one endpoint, returning queryable data from _myRepository1 or _myRepository2 with good performance ?


Solution

  • I've finally found a solution, but I'm not sure it's the most elegant :

    I've changed my Mapper.Map<> like this :

    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();
    }
    

    It returns a IQueryable<EntityB>, so my user can use OData filters. They are applied thanks to ApplyTo<> (so before the request is executed), which avoids to load the entire table in memory but only returns the fetched results.