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?
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).
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.
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 anIQueryable<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 theTResult
of each of thoseExpression<>
parameters may be different types you’ll need to abandon your dream of usingparams
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).
Show 1 more comment