Azureはじめました

Windows Azureで業務システムを組んでみる日記

OrderByのセレクタを外出ししたい

外部からのパラメータでリストの表示順位を変えたいなんてのは比較的ありがちなんだけど、EF+LINQでやろうとしてもいまいち方法がわからん。

ケース

素直にやるとこうなる。

  public class ViewModel{
    public string SearchText {get;set;}
    public int? age {get;set;}
    public int Order{get;set;}
    public int OrderDirection{get;set;}
  }
  public class Person{
    public int id {get;set;}
    public string Name {get;set;}
    public string FamilyName{get;set;}
    public int age{get;set;}
  }

  public IEnumerable<Person> search(ViewModel model){
    using (Entity entity = new Entity()){
      IQueryable<Person> query = entity.Persons ;
      if (!string.IsNullOrEmpty(model.SearchText))
        query = query.Where(p=>p.Name==SearchText || p.FamilyName==SearchText);
      if (model.age.HasValue)
        query = query.Where(p=>p.age==model.age.Value);

      if (model.OrderDirection==0){
        query = model.Order==0? query.OrderBy(p=>p.Name) : query.OrderBy(p=>p.id);
      }else{
        query = model.Order==0? query.OrderByDescending(p=>p.Name) : query.OrderByDescending(p=>p.id);
      }
      return query.Select(p=>p);
    }
  }

「model.Orderによって表示順を、model.OrderDirectionによって昇順降順を切り替えたい」ってのの実現に

      if (model.OrderDirection==0){
        query = model.Order==0? query.OrderBy(p=>p.Name) : query.OrderBy(p=>p.id);
      }else{
        query = model.Order==0? query.OrderByDescending(p=>p.Name) : query.OrderByDescending(p=>p.id);
      }

ってなるのがダサすぎる。
Orderbyのラムダを外出しにしてOrderDirectionによる分岐1個か、さらにそれも無くした形にしたい。

OrderByってどんな形になってたか

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(  this IEnumerable<TSource> source,  Func<TSource, TKey> keySelector )
Enumerable.OrderBy(TSource, TKey) メソッド (IEnumerable(TSource), Func(TSource, TKey)) (System.Linq)

thisがIEnumerableなので、上の例だと
IOrderedEnumerable OrderBy...と。
TKeyはKeySelectorにかかってるんだったか。

ということは

  Func<Person,int> f=(p)=>{return p.id;}
  query = query.OrderBy(f);

的な感じか。確かにコンパイルは通る。

型違いのデリゲートってどうするんだ?

上の例だとOrderByで切り替えたい並び替えは string(person.Name)とint(person.id)なので、

  Func<Person,int> f=(p)=>{return p.id;}
  Func<Person,string> f2=(p)=>{return p.Name;}
  query = query.OrderBy(model.Order==0?f:f2);

なんて甘い話は当然エラーになると。

'System.Func<Person,int>' と 'System.Func<Person,string>' の間に暗黙的な変換がないため、条件式の型がわかりません。

へちょん。

天才あらわる

what you are looking for is:

Expression<Func<Products, dynamic>>; 

try create a structure/class to hold both the Expression and if it is ascending or descending.

c# - Create an OrderBy Expression for LINQ/Lamda - Stack Overflow

f:id:twisted0517:20140601162236p:plain
dynamic!その手があったか!

  Expression<Func<Person,dynamic>> f;
  if (model.Order==0){
    f = p=> new{p.id};
  }else{ 
    f = p=> new{p.Name};
  }
  query = query.OrderBy(f);

コンパイルも実行も普通にできてしまった。
すげぇ。