プロジェクトでの開発時に、動的にLINQのWhereを生成できたら、もっとコードがスマートに書けるのに、、、と思いつつもプロジェクト中には時間がなく出来ませんでした。
そのため、次の機会に向けて対応できるようになりたいと思い、勉強した時のメモです。
以下のbeforeのように書いていたコードをafterのようなイメージに書けるようにすることを目標にしました。
Where条件のプロパティ名とキーワードを文字列で指定することが目標です。
before
var query = StudentList.AsQueryable().Where(null != Student.Name && Student.Name.Contains("A"));
after(※イメージです)
var query = StudentList.AsQueryable().Where("Name","A");
Expressionオブジェクトを利用すると動的に、式を組み立てられるということが分かりました。
そのため、まずWhereの引数に設定するラムダ式を勉強し、その後、ラムダ式を動的に組み立てる、Expressionについて勉強しました。
勉強の際には以下のサイトを参考にさせていただきました。
https://ufcpp.net/study/csharp/sp3_linq.html
https://ufcpp.net/study/csharp/sp3_expression.html
上記を勉強した後、以下のサイトを参考にafterになるように書いてみました。
ラムダ式、Expressionを勉強する前に以下のサイト見ても、あまり理解できず、自分の実施したい処理を書けるまでにはいたらなかったのですが、勉強後に見ると理解が進みました。
http://www.atmarkit.co.jp/fdotnet/dotnettips/986dynamiclinq/dynamiclinq.html
https://stackoverflow.com/questions/3703386/iqueryable-extension-create-lambda-expression-for-querying-a-column-for-a-keywo
勉強の結果、作成したコードが以下になります。Extensionsを使用すればもっときれいに書けると思いますが、現状はここまでとしています。
class Program
{
private static List<Student> StudentList;
static void Main(string[] args)
{
SetStudentsData();
var query1 = StudentList.AsQueryable().Where(GetWhereContainsExpression("Name","A"));
Console.WriteLine("Studentのフィルター結果(Name)");
foreach (var item in query1)
{
Console.WriteLine(item.Name.ToString());
}
var query2 = query1.AsQueryable().Where(GetWhereContainsExpression("Floor", "B")); ;
Console.WriteLine("Studentのフィルター結果(Floor)");
foreach (var item in query2)
{
Console.WriteLine("Name:{0},Floor:{1}",item.Name.ToString(),item.Floor.ToString());
}
Console.ReadLine();
}
public static Expression<Func<Student, bool>> GetWhereContainsExpression(string columnName, string keyword)
{
var type = typeof(Student);
var property = type.GetProperty(columnName);
var parameter = Expression.Parameter(type, "p");
// 文字列を指定してメソッドの情報を取得する
MethodInfo Contains = typeof(string).GetMethod("Contains");
//例)p.Nameの形式を作成
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
//例)null != p.Nameの形式を作成
var body0 = Expression.NotEqual(Expression.Constant(null), propertyAccess);
//p.Name.Contains(keyword)の形式を作成
var body1 = Expression.Call(propertyAccess,
Contains, Expression.Constant(keyword));
//例)null != p.Name && p.Name.Contains(keyword)の形式を作成
var newBody = Expression.AndAlso(body0, body1);
//例)p => null != p.Name && p.Name.Contains(keyword)の形式を作成
return Expression.Lambda<Func<Student, bool>>(newBody, parameter);
}
}
public class Student
{
public string Name { get; set; }
public string Number { get; set; }
public string Course { get; set; }
public string Floor { get; set; }
public Student(string name,string number,string course,string floor)
{
this.Name = name;
this.Number = number;
this.Course = course;
this.Floor = floor;
}
}