iT邦幫忙

DAY 20
2

Kuick Application & ORM Framework系列 第 20

Kuick -- 實作支援 Lambda Expression

要如何開發支援下圖呈現的 Lambda Expression 功能,本篇以 Kuick ORM 為例,詳細說明。

這篇分享主將細談到 Kuick OMR 對於 Lambda Expression 的實作,請先到 kuick.codeplex.com 下載原始檔。

Kuick ORM 裡有關於 Lambda Expression 的類別,包含下面 10 個類別:
01. Kuick.Data.Entity
02. Kuick.Data.Entity<T>
03. Kuick.Data.Sql
04. Kuick.Data.Sql<T>
05. Kuick.Data.SqlAggregate
06. Kuick.Data.SqlCriterion
07. Kuick.Data.SqlExpression
08. Kuick.Data.SqlOrderBy
09. Kuick.Data.SqlSet
10. Kuick.Data.DataUtility

這 10 個類別對於 Lambda Expression 功能的實作,收歛於 Kuick.Data.DataUtility 類別裡,DataUtility 包含 10 個方法,其中與 Lambda Expression 有關的方法有 8 個,依據方法的傳入參數分成 2 種:

一、傳入參數為『Expression<Func<T, object>>』的方法,其中 T 是指實作 IEntity 的物件
實作這個傳入參數,讓 Entity, Sql 相關的類別可以編寫 3 種表示式:
1. 欄位選取

public static Column ToColumn<T>(Expression<Func<T, object>> expression) where T : IEntity

2. 條件運算

public static SqlCriterion<T> ToSqlCriterion<T>(Expression<Func<T, object>> expression) where T : class, IEntity, new()
public static SqlExpression ToSqlExpression<T>(Expression<Func<T, object>> expression) where T : IEntity

3. 資料設定

public static SqlSet ToSqlSet<T>(Expression<Func<T, object>> expression) where T : IEntity

二、傳入參數為『Expression』的方法
擁有 Expression 傳入參數的方法,主要用途是提供給第一種方法的進階處理,分成 3 種作用:
1. 選取欄位
選取欄位是針對 Convert, MemberAccess, Call 的 ExpressionType 進行各別處理,請參閱原始程式。

internal static Column ToColumn<T>(Expression expression)
	where T : IEntity
{
	if(null == expression) { return null; }

	MemberInfo memberInfo = null;
	MemberExpression memberExpression = null;
	if(expression.NodeType == ExpressionType.Convert) {
		memberExpression = ((UnaryExpression)expression).Operand as MemberExpression;
		memberInfo = memberExpression.Member;
	} else if(expression.NodeType == ExpressionType.MemberAccess) {
		memberExpression = expression as MemberExpression;
		memberInfo = memberExpression.Member;
	} else if(expression.NodeType == ExpressionType.Call) {
		memberInfo = ((MethodCallExpression)expression).Method;
	} else {
		throw new NotImplementedException();
	}

	T schema = EntityCache.Find<T>();
	PropertyInfo info = memberInfo as PropertyInfo;
	string name = info.Name == Constants.Entity.KeyName
		? Reflector.GetValue(info, schema) as string
		: info.Name;
	Column column = schema.GetColumn(name);

	return column;
}

2. 建立查詢條件
這個方法對於 UnaryExpression, MemberExpression, BinaryExpression 進行各別處理,請參閱原始程式。

public static SqlCriterion<T> ToSqlCriterionMain<T>(Expression expression)
	where T : class, IEntity, new()
{
	SqlCriterion<T> c = new SqlCriterion<T>();

	// UnaryExpression
	UnaryExpression unaryExpression = expression as UnaryExpression;
	if(null != unaryExpression) {
		if(unaryExpression.NodeType == ExpressionType.Not) {
			c.SetNot();
		}
		c.Criteria.Add(ToSqlCriterionMain<T>(unaryExpression.Operand));
		//BinaryExpression binaryExp = (BinaryExpression)unaryExpression.Operand;
		//Column column = ToColumn<T>(binaryExp.Left);
		//SqlOperator opt = SqlExpression.FromExpressionType(binaryExp.NodeType);
		//object value = GetValue(binaryExp.Right);
		//c.Where(ToSqlExpression(column, opt, value));
		return c;
	}

	// MemberExpression
	MemberExpression memberExpression = expression as MemberExpression;
	if(null != memberExpression) {
		Column column = ToColumn<T>(expression);
		SqlExpression sqlExpression = SqlExpression.Column(column);
		c.Where(sqlExpression);
		return c;
	}

	// BinaryExpression
	BinaryExpression binaryExpression = expression as BinaryExpression;
	if(null != binaryExpression) {
		BinaryExpression innerBinaryExp = binaryExpression.Left as BinaryExpression;
		if(null == innerBinaryExp) {
			Column column = ToColumn<T>(binaryExpression.Left);
			SqlOperator opt = SqlExpression.FromExpressionType(expression.NodeType);
			object value = GetValue(binaryExpression.Right);
			c.Where(ToSqlExpression(column, opt, value));
			return c;
		} else {
			switch(expression.NodeType) {
				case ExpressionType.And:
					c.Logic = SqlLogic.And;
					break;
				case ExpressionType.Or:
					c.Logic = SqlLogic.Or;
					break;
				default:
					Logger.Error(
						"Kuick.Data.DataUtility.ToSqlCriterion",
						"Unhandled NodeType of BinaryExpression.",
						new Any("Expression", binaryExpression.ToString()),
						new Any("Node Type", expression.NodeType)
					);
					throw new NotImplementedException();
			}
			c.Where(ToSqlCriterionMain<T>(binaryExpression.Left));
			c.Where(ToSqlCriterionMain<T>(binaryExpression.Right));
			return c;
		}
	}

	// 
	throw new NotImplementedException(string.Format(
		"Not implemented the Expression type of '{0}'.",
		expression.GetType().Name
	));
}

3. 取值
這個方法對於 ConstantExpression, MemberExpression, NewExpression, MethodCallExpression, BinaryExpression 進行各別處理,請參閱原始程式。

public static object GetValue(MemberExpression expression)
{
	var objectMember = Expression.Convert(expression, typeof(object));
	var getterLambda = Expression.Lambda<Func<object>>(objectMember);
	var getter = getterLambda.Compile();
	return getter();
}

public static object GetValue(Expression expression)
{
	object value = null;

	// ConstantExpression
	ConstantExpression constExp = expression as ConstantExpression;
	if(null != constExp) { return constExp.Value; }

	// MemberExpression
	MemberExpression memExp = expression as MemberExpression;
	if(null != memExp) {
		value = GetValue(memExp);
		return value;
	}

	// NewExpression
	NewExpression newExp = expression as NewExpression;
	if(null != newExp) {
		List<object> list = new List<object>();
		foreach(Expression exp in newExp.Arguments) {
			object obj = GetValue(exp);
			list.Add(obj);
		}
		value = newExp.Constructor.Invoke(list.ToArray());
		return value;
	}

	// MethodCallExpression
	MethodCallExpression methodCallExp = expression as MethodCallExpression;
	if(null != methodCallExp) {
		ConstantExpression ce = methodCallExp.Arguments[0] as ConstantExpression;
		if(ce != null) {
			value = ce.Value.ToString();
			return value;
		}

		MemberExpression me = methodCallExp.Arguments[0] as MemberExpression;
		if(me != null) {
			value = GetValue(me);
			return value;
		}

		switch(methodCallExp.Method.Name) {
			case "Contains":
				value = " like '%" + value + "%'";
				return value;
			case "StartsWith":
				value = " like '" + value + "%'";
				return value;
			case "EndsWith":
				value = " like '%" + value + "'";
				return value;
			default:
				Logger.Error(
					"Kuick.Data.DataUtility.GetValue",
					"Unhandled Method of MethodCallExpression.",
					new Any("Expression", methodCallExp.ToString()),
					new Any("Method Name", methodCallExp.Method.Name)
				);
				throw new NotImplementedException();
		}
	}

	// BinaryExpression
	BinaryExpression binaryExp = expression as BinaryExpression;
	if(null != binaryExp) {
		object left = GetValue(binaryExp.Left);
		object right = GetValue(binaryExp.Right);

		// String
		if(binaryExp.Type.IsString()) {
			value = string.Concat(left, right);
			return value;
		}

		// Integer
		if(binaryExp.Type.IsInteger()) {
			switch(binaryExp.NodeType) {
				case ExpressionType.Add:
					value = (int)left + (int)right;
					break;
				case ExpressionType.Subtract:
					value = (int)left - (int)right;
					break;
				case ExpressionType.Negate:
					value = -(int)left;
					break;
				case ExpressionType.Multiply:
					value = (int)left * (int)right;
					break;
				case ExpressionType.Increment:
					value = (int)left + 1;
					break;
				case ExpressionType.Divide:
					value = (int)left / (int)right;
					break;
				default:
					Logger.Error(
						"Kuick.Data.DataUtility.GetValue",
						"Unhandled NodeType of BinaryExpression.",
						new Any("Expression", binaryExp.ToString()),
						new Any("Value Type", binaryExp.Type.Name),
						new Any("Node Type", binaryExp.NodeType)
					);
					throw new NotImplementedException();
			}
			return value;
		}

		// Boolean
		if(binaryExp.Type.IsBoolean()) {
			switch(binaryExp.NodeType) {
				case ExpressionType.And:
				case ExpressionType.AndAlso:
					value = (bool)left && (bool)right;
					break;
				case ExpressionType.Or:
				case ExpressionType.OrAssign:
					value = (bool)left || (bool)right;
					break;
				case ExpressionType.IsTrue:
					value = null == left ? (bool)right : (bool)left;
					break;
				case ExpressionType.IsFalse:
					value = null == left ? !(bool)right : !(bool)left;
					break;
				default:
					Logger.Error(
						"Kuick.Data.DataUtility.GetValue",
						"Unhandled NodeType of BinaryExpression.",
						new Any("Expression", binaryExp.ToString()),
						new Any("Value Type", binaryExp.Type.Name),
						new Any("Node Type", binaryExp.NodeType)
					);
					throw new NotImplementedException();
			}
			return value;
		}
	}

	Logger.Error(
		"Kuick.Data.DataUtility.GetValue",
		"Unhandled Expression Type.",
		new Any("Type Name", expression.GetType().Name)
	);
	throw new NotImplementedException();
}

近日 Kuick 將有新的一版更新,如果您發現有錯誤,歡迎您直接與我聯絡 kevinjong@gmail.com 謝謝。

====================================================

這篇分享提及 Sql 操作相關的類別,下篇分享 (Sql Command 物件化) 來說說 Sql Command 與這些類別之間的關係:
01. Kuick.Data.Sql
02. Kuick.Data.Sql<T>
03. Kuick.Data.SqlAggregate
04. Kuick.Data.SqlExpression
05. Kuick.Data.SqlCriterion
06. Kuick.Data.SqlCriterion<T>
07. Kuick.Data.SqlJoin
08. Kuick.Data.SqlLiteral
09. Kuick.Data.SqlOrderBy
10. Kuick.Data.SqlSet

========================================
鐵人賽分享列表:Kuick Application & ORM Framework
開放原始碼專案:kuick.codeplex.com
直接下載原始碼:Kuick
下載相關文件檔:C# Code Conventions and Design Guideline
相關教學影片區:Kuick on YouTube


上一篇
Kuick -- 設計期 vs. 執行期,設計議題 vs. 部署議題
下一篇
Kuick -- Sql Command 物件化
系列文
Kuick Application & ORM Framework34

2 則留言

0
patrickcheng
iT邦新手 4 級 ‧ 2012-10-28 11:07:35

對於國人有志於Framework 研發,我一定支持!

kevinjong iT邦新手 3 級 ‧ 2012-10-28 11:41:00 檢舉

謝謝
~ 中K

我要留言

立即登入留言