Github Link : Trace-Dapper.NET-Source-Code
7. Strongly Typed Mapping Part4: Expression version
With the previous logic, we use Expression to implement dynamic creation methods, and then we can think Why use Expression implementation first instead of Emit?
In addition to the ability to dynamically build methods, compared to Emit, it has the following advantages:
Readadable
, You can use familiar keywords, such as the Variable corresponds to Expression.Variable, and the creation of object New corresponds to Expression.New
Easy Runtime Debug
, You can see the logic code corresponding to Expression in Debug mode
So it is especially suitable for introducing dynamic method establishment, but Expression cannot do some detailed operations compared to Emit, which will be explained by Emit later.
Rewrite Expression version
Logic:
- Get all field names of sql select
- Obtain the attribute data of the mapping type > encapsulate the index, sql field and class attribute data in a variable for later use
- Dynamic create method: Read the data we want from the database Reader in order, the code logic:
User dynamic method ( 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;
}
Finally, the following Exprssion version code
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. Get all field names in sql select
var names = Enumerable . Range ( 0 , reader . FieldCount ). Select ( index => reader . GetName ( index )). ToArray ();
// 2. Get the attribute data of the mapping type > encapsulate the index, sql fields, and class attribute data in a variable for later use
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. Dynamic creation method: read the data we want from the database Reader in order
/*Method logic:
User dynamic method (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 >();
{
// method(IDataReader reader)
var exParam = Expression.Parameter(typeof(DbDataReader), "reader");
// Mapping class object = new Mapping class();
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();
}
}
}
Finally, check Expression.Lambda > DebugView (note that it is a non-public attribute) :
.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
}
}
Top comments (0)