I have an abstract class called Fruit. I then have a derived class called Apple.
I have these two extension methods:
public static IQueryable<TFruit> WithEagerLoading<TFruit>(this IQueryable<TFruit> query) where TFruit : Fruit
{
return query.EagerLoad(x => x.Distributors); // Fruit.Distributors
}
public static IQueryable<Apple> WithEagerLoading(this IQueryable<Apple> query)
{
query = query.EagerLoad(x => x.AppleBrands); // Apple.AppleBrands
// now bubble up to base extension method
return query.WithEagerLoading<Apple>();
}
Now, here is the generic method i have in a Repository:
public TFruit FindById<TFruit>(int fruitId) where TFruit : Fruit
{
var query = _ctx.Fruits // IQueryable<Fruit>
.OfType<TFruit>(); // IQueryable<TFruit>
query = query.WithEagerLoading();
return query.SingleOrDefault(x => x.FruitId == fruitId);
}
The problem i have, is when i do this:
var apple = repository.FindById<Apple>(1);
It goes into the IQueryable<Fruit>
extension method.
I want it to go into IQueryable<Apple>
extension method. For other types of Fruit, it should go into the IQueryable<TFruit>
extension method.
I thought the compiler would pick the most specific extension method.
Any ideas?
EDIT
Thanks for the comments/answer. I see now why this doesn't work.
So what are options to resolve this? If i create a method:
public static IQueryable<Apple> WithAppleEagerLoading(this IQueryable<Apple> query)
How would i call it from my generic repository? I would have to inspect the type of TFruit
:
public TFruit FindById<TFruit>(int fruitId) where TFruit : Fruit
{
var query = _ctx.Fruits // IQueryable<Fruit>
.OfType<TFruit>(); // IQueryable<TFruit>
if (typeof(TFruit) == typeof(Apple))
query = query.WithAppleEagerLoading();
else
query = query.WithEagerLoading();
return query.SingleOrDefault(x => x.FruitId == fruitId);
}
Which isn't nice - considering i have around 20 derived types.
Can anyone offer an alternative as to what i'm attempting to do?
Extension method resolution happens at compile time. The query variable is of type IQueryable<TFruit>
, so the compiler picks the most specific method that matches WithEagerLoading<Fruit>
. It can't pick the apple one because it only knows that TFruit
is some kind of Fruit
. It picks it once, and permanently.
What you're suggesting would require it to dynamically decide which extension method to use based on the type at runtime, or to compile separate versions of IQueryable<TFruit>
that resolve methods differently based on specific values of TFruit
.
Edit to answer additional question
Well, the special casing isn't super horrible, because you can use a switch statement. But I agree, not ideal if you have a lot of types. In terms of delegating to subclasses, I'd adjust Paul's answer a bit:
abstract class FruitRepository : IRepository<T> where TFruit : Fruit
{
public TFruit FindByID(int fruitID)
{
//query stuff here
query = AddEagerLoading(query)
.WithEagerLoading();
}
//this could also be abstract to prevent you from forgetting
public virtual IQueryable<TFruit> AddEagerLoading(IQueryable<TFruit> query)
{
return query;
}
}
and then
class AppleRepository : FruitRepository<Apple>
{
public override AddEagerLoading(IQueryable<Apple> query)
{
return query.EagerLoad(x => x.AppleBrands);
}
}
So that way you have the minimal code per subclass.