0

I want to use some System.Linq extension methods in a base class whereas derived classes should be able to provide some expressions by overriding specific methods.

Current base class code:

lQuery.OrderBy(s => s.ID) <-- works, *1

I want to replace s => s.ID by a method call that can be overridden:

... Expression<Func<TSource, TKey>> GetOrderByKey<TSource, TKey>()
{
  ...
} 

But when I override the method like

override Expression<Func<TSource, TKey>> GetOrderByKey<TSource, TKey>()
{
    return s => s.ID; <-- compiler error, *2
}

the compiler outputs the error

Cannot implicitly convert type 'int' to 'TKey'

  1. Why is the generic parameter TKey magically deduced (and to what?) in line 1 but in line 2 it is deduced to the actual type of the ID property?

  2. How to resolve the compiler error?

Thanks in advance.

Edit

I struggle to find the right explanation of the issue. Let's break it down to the following simple lines:

  var lQuery = from s in ... select s;

  lQuery = lQuery.OrderBy(s => s.ID); // *3

How exactly is that OrderBy call interpreted by the compiler as it results in an ORDER BY ID ASC instead of ORDER BY %Value of ID% ASC? The compiler seems to somehow decide to deduce s.ID to property name "ID" instead of taking the actual property data type and thus the int value.

Edit2

Ok, another example for +D Stanley

This works:

void SetOrder<TKey>(IQueryable<%type of s%> aQuery, Expression<Func<%type of s%, TKey>> aKeySelector)
{
    aQuery.OrderBy(aKeySelector);
}

...

SetOrder(aQuery, s => s.ID); // <-- works

But this not (the compile error is display as mentioned before)

protected void SetOrder<TKey>(IQueryable<%type of s%> aQuery)
{
    Expression<Func<%type of s%, TKey>> lKeySelector = s => s.ID; // <-- deduced to int

    aQuery.OrderBy(lKeySelector);
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
ViRuSTriNiTy
  • 5,017
  • 2
  • 32
  • 58
  • Has to be a `Func` for the `OrderBy`, if I remember correctly. You could remove the `Expression` bit, I think.. – code4life Oct 13 '15 at 16:29
  • By the time you're overriding your GetOrderByKey() method, you ought to know what type `TKey` is, so it should be an explicit parameter on your class override: `class FooRepo : FooBase`. That means the generic parameters for TKey and TSource should be on the class itself, not on this method. – StriplingWarrior Oct 13 '15 at 16:30
  • Can you show some more context around *1? There's not enough information to see why it works there. – Erik Oct 13 '15 at 16:30
  • 1
    Can you the class definitions ? I suspect you may be re-using `TKey` when it's already defined at the class level. – D Stanley Oct 13 '15 at 16:33
  • TSource must implement IComparable interface. Please see: https://msdn.microsoft.com/en-us/library/vstudio/system.icomparable%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396 – Maciej Los Oct 13 '15 at 16:47
  • +Maciej Los Can you please fix the link? This might be the solution but i cannot open it. – ViRuSTriNiTy Oct 13 '15 at 16:49
  • @ViRuSTriNiTy, done! – Maciej Los Oct 13 '15 at 16:49
  • +Maciej Los k, thx. Unfortunately implementing that interface wont work imho. See my edit for a better example of the issue, +D Stanley nope TKey is mysteriously introduced by System.Linq.IQueryable, +Erik see the additional example added – ViRuSTriNiTy Oct 13 '15 at 16:54
  • 2
    Your question of "How does `OrderBy` work" is pretty straightforward, but I doubt it will help you fix your issue. You need to post the class definition and more context to understand what the problem is. – D Stanley Oct 13 '15 at 16:55
  • @ViRuSTriNiTy There's no "magic" with `TKey` in `IQueryable` - it's just a generic parameter. – D Stanley Oct 13 '15 at 16:56
  • +D Stanley Please see Edit2, i don't know how to describe it in another way. – ViRuSTriNiTy Oct 13 '15 at 17:07
  • 1
    @ViRuSTriNiTy, after all those edits... seems you want to define custom selector to provide sort on this field (property). Am i right? – Maciej Los Oct 13 '15 at 17:36
  • +Maciej Los yes partially right, i simply want the derived class to provide a custom selector for the base class _but_ this selector should not be specific to a concrete type. That's why i struggle to make TKey "dynamic". – ViRuSTriNiTy Oct 14 '15 at 05:11

2 Answers2

1

How does OrderBy infer what the types of TSource and TKey are

The compiler can infer the types of TSource and TKey by looking at:

  1. the type of lQuery - in this case, IQueryable<{your class}>
  2. the return type of the expression, in this case, the type of the ID property of {your class}

How to resolve the compiler error?

You're asking the caller to specify the type of TKey (via the generic parameter), but in your method you are specifying an expression of a known type. You can just remove the generic argument:

protected void SetOrder(IQueryable<%type of s%> aQuery)
{
  Expression<Func<%type of s%, int>> lKeySelector = s => s.ID; 

  aQuery.OrderBy(lKeySelector);
}

But note that OrderBy returns a query - it does not change the original query, so what you really want is something like:

protected IQueryable<%type of s%> SetOrder(IQueryable<%type of s%> aQuery)
{
  Expression<Func<%type of s%, int>> lKeySelector = s => s.ID; 

  return aQuery.OrderBy(lKeySelector);
}

or possibly

protected void SetOrder(ref IQueryable<%type of s%> aQuery)
{
  Expression<Func<%type of s%, int>> lKeySelector = s => s.ID; 

  aQuery = aQuery.OrderBy(lKeySelector);
}

But using ref parameters is generally discouraged - it is preferable to return a new value rather than changing the original value.

ViRuSTriNiTy
  • 5,017
  • 2
  • 32
  • 58
D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • Thanks for putting time into answering but i think i have written my question in a way that no one understands my intentions. I don't want to get rid of the compiler error as this is just a side effect. I'm trying to create a method that can be overridden by a derived class to provide a custom selector for the base class _but_ this selector should not be specific to a concrete type. That's why i struggle to make TKey "dynamic". I've found a solution myself already that fits my needs, see my answer. – ViRuSTriNiTy Oct 14 '15 at 13:00
0

I think i have written my question in a way that no one understands my intentions. My goal was to avoid writing the same .OrderBy() / .ThenBy() code (*1) again and again for each field that needs to be sorted.

So i got the idea of adding a SortKey into the mix and when a SortKey matches (*2) then i call an extension method that does the appropriate calls for sorting (*3). Works like a charm.

IQueryable extension method

public static System.Linq.IQueryable<TRecord> Sort<TRecord, TKey>(this System.Linq.IQueryable<TRecord> aQuery, 
  System.Linq.Expressions.Expression<System.Func<TRecord, TKey>> aSortFieldSelector, SortOrder aSortOrder, bool aIsPrimarySort)
{
  System.Linq.IOrderedQueryable<TRecord> lOrderedQuery = aIsPrimarySort ? null : aQuery as System.Linq.IOrderedQueryable<TRecord>;

  // *1
  if (aSortOrder == SortOrder.Descending)
  {
    if (lOrderedQuery == null)
      lOrderedQuery = aQuery.OrderByDescending(aSortFieldSelector);
    else
      lOrderedQuery = lOrderedQuery.ThenByDescending(aSortFieldSelector);
  }
  else
  {
    if (lOrderedQuery == null)
      lOrderedQuery = aQuery.OrderBy(aSortFieldSelector);
    else
      lOrderedQuery = lOrderedQuery.ThenBy(aSortFieldSelector);
  }

  if (lOrderedQuery != null)
    return lOrderedQuery.AsQueryable();
  else
    return aQuery;
}    

Base class

public class BaseClass
{
  // public
    public void DoSomething()
    {
      System.Linq.IQueryable<TRecord> lQuery = ...
      System.Collections.Generic.Dictionary<string, SortOrder> lSorting = ...

      ...
      SortQuery(lQuery, lSorting);
      ...
    }

  // protected
    protected abstract void SortQuery(ref System.Linq.IQueryable<TRecord> aQuery, string aSortKey, SortOrder aSortOrder, bool aIsPrimarySort);

  // private
    private void SortQuery(ref System.Linq.IQueryable<TRecord> aQuery, System.Collections.Generic.Dictionary<string, SortOrder> aSorting)
    {
      bool lIsPrimarySort = true;

      foreach (string lSortKey in aSorting.Keys)
      {
        SortQuery(ref aQuery, lSortKey, aSorting[lSortKey], lIsPrimarySort);

        lIsPrimarySort = false;
      }
    }
}

Derived class

public class DerivedClass : BaseClass
{
  // protected
    protected override void SortQuery(ref IQueryable<CouponTableRecord> aQuery, string aSortKey, SortOrder aSortOrder, bool aIsPrimarySort)
    {
      if (aSortKey == SortKeys.ID) // *2
        aQuery = aQuery.Sort(r => r.ID, aSortOrder, aIsPrimarySort); // *3
      else
      if (aSortKey == SortKeys.Caption) 
        aQuery = aQuery.Sort(r => r.Caption, aSortOrder, aIsPrimarySort);
      else
        ...
    }

  // private
    private class SortKeys
    {
      public const string ID = "ID";
      public const string Caption = "Caption";
      ...
    }      
}

Regarding my question

How exactly is that OrderBy call interpreted by the compiler as it results in an ORDER BY ID ASC instead of ORDER BY %Value of ID% ASC? The compiler seems to somehow decide to deduce s.ID to property name ID instead of taking the actual property data type and thus the int value.

I found an interesting blog and also a SO answer about this. Basically it is done by drilling into the expression body to get the member name:

Expression<Func<int>> expression = () => Sample.Foo;
MemberExpression body = (MemberExpression)expression.Body;
string name = body.Member.Name;
ViRuSTriNiTy
  • 5,017
  • 2
  • 32
  • 58