iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 7
3
Software Development

🌊 進階學習 ADO.NET、Dapper、Entity Framework 系列 第 7

【深入Dapper.NET源碼】Strongly Typed Mapping 原理 Part4 : Expression版本

有了前面的邏輯,就著使用Expression實作動態建立方法。

為何先使用 Expression 實作而不是 Emit ?

除了有能力動態建立方法,相比Emit有以下優點 :

  • 可讀性好,可用熟悉的關鍵字,像是變數Variable對應Expression.Variable、建立物件New對應Expression.New
    https://ithelp.ithome.com.tw/upload/images/20190920/20105988rkSmaILTw7.png
  • 方便Runtime Debug,可以在Debug模式下看到Expression對應邏輯代碼
    https://ithelp.ithome.com.tw/upload/images/20190920/201059882EODD9OdnD.png
    https://ithelp.ithome.com.tw/upload/images/20190920/201059882gSYyfUduS.png

所以特別適合介紹動態方法建立,但Expression相比Emit無法作一些細節操作,這點會在後面Emit講解到。

改寫Expression版本

邏輯 :

  1. 取得sql select所有欄位名稱
  2. 取得mapping類別的屬性資料 > 將index,sql欄位,class屬性資料做好對應封裝在一個變數內方便後面使用
  3. 動態建立方法 : 從資料庫Reader按照順序讀取我們要的資料,其中代碼邏輯 :
User 動態方法(IDataReader reader)
{
	var user = new User();
	var value = reader[0];
	if( !(value is System.DBNull) )
		user.Name = (string)value;
	value = reader[1];
	if( !(value is System.DBNull) )
		user.Age = (int)value;	
	return user;
}

最後得出以下Exprssion版本代碼

public static class DemoExtension
{
	public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql) where T : new()
	{
		using (var command = cnn.CreateCommand())
		{
			command.CommandText = sql;
			using (var reader = command.ExecuteReader())
			{
				var func = CreateMappingFunction(reader, typeof(T));
				while (reader.Read())
				{
					var result = func(reader as DbDataReader);
					yield return result is T ? (T)result : default(T);
				}

			}
		}
	}

	private static Func<DbDataReader, object> CreateMappingFunction(IDataReader reader, Type type)
	{
		//1. 取得sql select所有欄位名稱
		var names = Enumerable.Range(0, reader.FieldCount).Select(index => reader.GetName(index)).ToArray();

		//2. 取得mapping類別的屬性資料 >  將index,sql欄位,class屬性資料做好對應封裝在一個變數內方便後面使用
		var props = type.GetProperties().ToList();
		var members = names.Select((columnName, index) =>
		{
			var property = props.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
			?? props.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
			return new
			{
				index,
				columnName,
				property
			};
		});

		//3. 動態建立方法 : 從資料庫Reader按照順序讀取我們要的資料
		/*方法邏輯 : 
			User 動態方法(IDataReader reader)
			{
				var user = new User();
				var value = reader[0];
				if( !(value is System.DBNull) )
					user.Name = (string)value;
				value = reader[1];
				if( !(value is System.DBNull) )
					user.Age = (int)value;	
				return user;
			}
		*/
		var exBodys = new List<Expression>();
		{
			// 方法(IDataReader reader)
			var exParam = Expression.Parameter(typeof(DbDataReader), "reader");

			// Mapping類別 物件 = new Mapping類別();
			var exVar = Expression.Variable(type, "mappingObj");
			var exNew = Expression.New(type);
			{
				exBodys.Add(Expression.Assign(exVar, exNew));
			}

			// var value = defalut(object);
			var exValueVar = Expression.Variable(typeof(object), "value");
			{
				exBodys.Add(Expression.Assign(exValueVar, Expression.Constant(null)));
			}


			var getItemMethod = typeof(DbDataReader).GetMethods().Where(w => w.Name == "get_Item")
				.First(w => w.GetParameters().First().ParameterType == typeof(int));
			foreach (var m in members)
			{
				//reader[0]
				var exCall = Expression.Call(
					exParam, getItemMethod,
					Expression.Constant(m.index)
				);

				// value = reader[0];
				exBodys.Add(Expression.Assign(exValueVar, exCall));

				//user.Name = (string)value;
				var exProp = Expression.Property(exVar, m.property.Name);
				var exConvert = Expression.Convert(exValueVar, m.property.PropertyType); //(string)value
				var exPropAssign = Expression.Assign(exProp, exConvert);

				//if ( !(value is System.DBNull))
				//		(string)value
				var exIfThenElse = Expression.IfThen(
					Expression.Not(Expression.TypeIs(exValueVar, typeof(System.DBNull)))
					, exPropAssign
				);

				exBodys.Add(exIfThenElse);
			}


			// return user;	
			exBodys.Add(exVar);

			// Compiler Expression 
			var lambda = Expression.Lambda<Func<DbDataReader, object>>(
				Expression.Block(
					new[] { exVar, exValueVar },
					exBodys
				), exParam
			);

			return lambda.Compile();
		}
	}
}

查詢效果圖 :
20191004205645.png

最後查看Expression.Lambda > DebugView(注意是非公開屬性)驗證代碼 :

.Lambda #Lambda1<System.Func`2[System.Data.Common.DbDataReader,System.Object]>(System.Data.Common.DbDataReader $reader) {
    .Block(
        UserQuery+User $mappingObj,
        System.Object $value) {
        $mappingObj = .New UserQuery+User();
        $value = null;
        $value = .Call $reader.get_Item(0);
        .If (
            !($value .Is System.DBNull)
        ) {
            $mappingObj.Name = (System.String)$value
        } .Else {
            .Default(System.Void)
        };
        $value = .Call $reader.get_Item(1);
        .If (
            !($value .Is System.DBNull)
        ) {
            $mappingObj.Age = (System.Int32)$value
        } .Else {
            .Default(System.Void)
        };
        $mappingObj
    }
}

20191005035640.png-


上一篇
【深入Dapper.NET源碼】Strongly Typed Mapping 原理 Part3 : 動態建立方法重要概念「結果反推程式碼」優化效率
下一篇
【深入Dapper.NET源碼】Strongly Typed Mapping 原理 Part5 : Emit IL反建立C#代碼
系列文
🌊 進階學習 ADO.NET、Dapper、Entity Framework 30

尚未有邦友留言

立即登入留言