Cast IQueryable<EntityObject> to IQueryable<Specific>
Asked Answered
W

5

13

We are trying to cast an instance of IQueryable<EntityObject> to an IQueryable<SpecificEntityObject>, the SpecificEntityObject type is only known at runtime.

We have tried using the code below, which does not compile because The type or namespace 'objType' does not exist.

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

Any ideas?

Worshipful answered 11/1, 2012 at 12:7 Comment(5)
Basically, you can't do that conveniently - you'd have to use reflection, and then typed is going to be object or non-generic IQueryable, and none of the other stuff will work. There's things you can do here, but pretty much all of them will be ugly as anything... there's no magic bullet here. As a side note, the Cast call would be to <t>, not to <IQueryable<t>> (if you'll forgive the awkward pseudo-syntax there)Adrell
What exactly are you trying to do: cast the result of a query into a type you only know at runtime, or extend the query to express a cast operation to a specific type you only know at runtime?Ecbolic
Why do you want to do this? How come you say you know the type only at runtime, but then use it like you knew it at compile time?Interactive
We're trying to create a generic reporting API for our application. So at all stages in the applications development we will never know of specific types. We have various tables, but we have stored all these in list<EntityObject> so when we come to try and GroupBy things our GroupBy is reflecting on EntityObject and finding it doesn't have the properties we require as the base type is EntityObject and not a specific type - we are now at the point we were feel our design could be wrong.. any ideas on how to GroupBy dynamically? - Does that even make sense?Worshipful
We sorted all this another way... we restructured how we arrange our queries... now we (using Dynamic Linq) we created a new group key and pass that in as a string which is parsed in - thanks for you help!Worshipful
K
8

Use following IQueryable extension generic method 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();
    }
}
Kunlun answered 11/1, 2012 at 12:22 Comment(2)
Nice code and I use it in my project. One little notice: destinationList.Add(destElement); should be out of the brackets.Flying
It's weird OP has accepted this as answer. Not only does she not have TDest at compile time, this approach eagerly loads everything into memory which sucks for large data.Huffish
A
1

For anyone else wanting to to project non-db values from a db query, this project from u/Luis Aguilar was very, very helpful to me.

I had a very large legacy database (450GB) which was required to be served to OData/WebAPI.

The OData requirement meant I could not filter the source data (much) before returning it to the user. We could silo it, but apart from that it is their data to query as they wish.

More importantly, however, the legacy data was far too convoluted to expose as-is, and there was significant business logic required to collate the necessary data (Include of navigation properties/foreign keys, lengthy clause predicates, etc).

This meant the pagination and result limiting would not be available until after the query was already materialized.

Normal shortcuts for this kind of thing involve various strategies that involve materialization/eager loading. However, due to the size of the dataset and lack of filtering, this would result in massive process memory bloat and out-of-memory crashes.

So, some code. Here's my config call, similar to what AutoMapper or OData require:

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

This design allows me to keep the projection rules in the DTO class with the rest of the business logic. Here's what the DTO-level code looks like:

public static void ProjectionModel(ProjectionModelBuilder modelBuilder)
{
    modelBuilder
        .Projection<ClientDTO>()
        .ForSource<Client>(configuration =>
        {
            configuration.Property(dto => dto.Name).ExtractFrom(entity => entity.Name);
            // etc
        });
}

Where Client is my Entity/EDM type, mapped to db table and a gazillion foreign keys.

To then get a translated/projected Queryable, this is it:

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

Only the last two lines are relevant, but included the rest for context.

The last thing I had to do was set this.IsAutoConfigured = false; in the constructor for ProjectionSourceTypeConfiguration.cs in Luis' code; this allowed me to order my projection definitions manually so navigation properties inside parent classes would configure their projections successfully.

I can't thank https://stackoverflow.com/users/543712/luis-aguilar enough for his work. After writing my own LINQ Provider/ExpressionVisitor with various generic method invocations, translations and treewalks to still have various problems, his project was a godsend.

If you do find to have to pipeline your own expression processing for performance or other reasons, I'd recommend these two answers to begin with.

Antemundane answered 8/12, 2015 at 6:51 Comment(0)
H
0

If you don't have compile time type info, you have to rely on ugly reflection code all the way down. dynamic keyword might make things a bit tidy. Try something like:

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.

There is also nice little extension method:

public static IQueryable Cast(this IQueryable source, Type type)

in System.Linq.Dynamic.Core library.

Huffish answered 5/12, 2021 at 9:36 Comment(0)
P
-1

If you started using reflection, you need to use it with all methods too. So you need to create

var myEnumType = typeof(IEnumerable<>).MakeGenericType(objType);

and also find extention method Cast matching needed type also at runtime.

 typeof(Enumerable).GetMethod("Cast", BindingFlags.Public |
                BindingFlags.Static, 
                null, 
                CallingConventions.Any,  
                new Type[] {typeof(object)}, 
                null);

then you would be able to call that method

Protract answered 11/1, 2012 at 12:17 Comment(1)
myEnumType.GetMethod doesn't work here since Cast is not a static/instance method on type IEnumerable<>. You need typeof(Enumerable).GetMethodHuffish
E
-1
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());
Everyday answered 6/3, 2014 at 1:30 Comment(1)
Hi, welcome to SO. Could you give some explanation for your solution?Antonelli

© 2022 - 2024 — McMap. All rights reserved.