EF Core expression with object instead of generic parameter

I have a method which searches for an entity, and optionally loads navigation properties:

public async Task<Person?> FindPerson<TProperty>(string name, params Expression<Func<Person, IEnumerable<TProperty>>>[] includes) where TProperty : class
{
  var person = await Find(name);
  if (person != null)
  {
    foreach (var include in includes)
      await _context.Entry(person).Collection(include).LoadAsync();
  }
}

(Note that is a very simplified version of my real code; the content is irrelevant, the method signature is the problem.)

I want the callsite to be:

var person = await FindPerson("Mike");
// or
var person = await FindPerson("Mike", x => x.Account);

But the first one won’t work because I haven’t specified <TProperty> (“The type arguments cannot be inferred from the usage”).

A workaround is this signature:

public async Task<Person?> FindPerson(string name, params Expression<Func<Person, IEnumerable<object>>>[] includes)

What are the implications of changing generic parameter TProperty to type object? Assuming it works in this narrow case, can it blow up in some other case I haven’t encountered? Or would there be performance issues I should take into account?

  • Another (safe) workaround is to have two overloads, but it introduces a bit of duplication in my case that I’d like to avoid, if possible.

    – 




  • Why aren’t you using your Expression<T> objects to build-up an IQueryable<T> and then executing it only once? Instead your code seems to execute the query for every argument – that seems slow and wasteful…

    – 

  • @Dai I agree, your way is better… but that code is not real, it was just something to put into the method body. It’s the signature I’m interested in. I’m generally interested to know the drawbacks of using object in such cases.

    – 




  • Okay, I think I understand your point now. So, what you’re asking isn’t really possible in C# today because C# does not support variadic generic types (or any kind of higher-kinded type, really) (which is why .NET has 12+ different versions of Tuple<T0,T1>, Tuple<T0,T1,T2>, etc. Because the TResult of each of those Expression<> parameters may be different types you’ll need to abandon your dream of using params and settle for some kind of chained generic struct…

    – 

  • @Dai A quick SO search for “chained generic struct” yields no insight… what is that? Regarding the tuple analogy, yep, that’s what I’ve done (implemented a number of overloads).

    – 

From my comment:

Okay, I think I understand your point now. So, what you’re asking isn’t really possible in C# today because C# does not support variadic generic types (or any kind of higher-kinded type, really) (which is why .NET has 12+ different versions of Tuple<T0,T1>, Tuple<T0,T1,T2>, etc. Because the TResult of each of those Expression<> parameters may be different types you’ll need to abandon your dream of using params and settle for some kind of chained generic struct…

I meant something like this:

public interface IThing<TEntity>
    where TEntity : class
{
    IQueryable<TEntity> Apply( IQueryable<TEntity> query );
}

public readonly struct Thing<TEntity, TProperty, TPrev > : IThing<TEntity>
    where TEntity : class
    where TPrev : struct, IThing<TEntity>
{
    private readonly TPrev                               prev;
    private readonly Expression<Func<TEntity,TProperty>> expr;
    
    public Thing( TPrev prev, Expression<Func<TEntity,TProperty>> expr )
    {
        this.prev = prev;
        this.expr = expr;
    }
    
    public Thing< TEntity, TNextProperty, /*TPrev:*/ Thing<TEntity,TProperty,TPrev> > Next<TNextProperty>( Expression<Func<TEntity,TNextProperty>> expr )
    {
        return new Thing<TEntity, TNextProperty, Thing<TEntity,TProperty,TPrev> >( this, expr );
    }
    
    public IQueryable<TEntity> Apply( IQueryable<TEntity> query )
    {
        query = EntityFrameworkQueryableExtensions.Include( source: query, navigationPropertyPath: this.expr );
        
        return this.prev.Apply( query );
    }
}

public readonly struct Start<TEntity> : IThing<TEntity>
    where TEntity : class
{
    public Thing< TEntity, TNextProperty, /*TPrev:*/ Start<TEntity> > Next<TNextProperty>( Expression<Func<TEntity,TNextProperty>> expr )
    {
        return new Thing<TEntity, TNextProperty, Start<TEntity> >( this, expr );
    }
    
    public IQueryable<TEntity> Apply( IQueryable<TEntity> query )
    {
        // Base case: just return directly.
        return query;
    }
}

Which can be used like so:

public class Person
{
    public String Name { get; }
    
    public DateOnly DateDiscombobulated { get; }
    
    public Int32 NumnberOfTurkeyTwizzlersDeterged { get; }
    
    //
    
    public Family Family { get; }
    
    public IList<DustBunny> SecretShames { get; }
}

public class Family
{
}

public class DustBunny
{
}

//

IQueryable<Person> query = dbContext.People;
    
IQueryable<Person> mutatedQuery = new Start<Person>()
    .Next( p => p.Name )
    .Next( p => p.DateDiscombobulated )
    .Next( p => p.NumnberOfTurkeyTwizzlersDeterged )
    .Apply( query );

Proptip: load that into your editor and look at the inferred type of the result of the last .Next() call: 100% of the generic type information is being preserved. Using recursive-generics via chaining is the closest thing to variadic generics we can accomplish in C# today).

enter image description here


Of course my example above is more verbose than your original example, but with some subtle changes you can make it succint:

For example, if you add a new extension method to use it, and rename Next() to N(), and pass the IQueryable<Person> in from the start you can skip the .Apply call and end up with something like this:

List<Person> list = await dbContext.People.N( p => p.Name ).N( p => p.DateDiscombobulated ).N( p => p.NumnberOfTurkeyTwizzlersDeterged ).ToListAsync();

Use this advice at your own peril.

Leave a Comment