Dynamic Method Invocation in C# with PetaPoco (Part 2)

In Part 1 I looked at how PetaPoco used Dynamic Method Invocation to return a method that could convert an IDataReader column into a  Dynamic ExpandoObject. Now its time to look at the the other functionality used to return scalars and POCOs.

Scalars

In PetaPoco a scalar value is returned in the following way:

string column = db.ExecuteScalar<string>("SELECT Column FROM Table");

The functionality to create a factory to get the scalar values is part of the same GetFactory method covered in the previous post. I will pick up the method body when we already have the IlGenerator. After a check to see if the required type is a value type, a string or a Byte[] (indicating a scalar value) the method body is built up with IL. As before there is logic to perform some conversion for particular IDataReader implementations, and once again I am going to ignore it for the sake of brevity.

il.Emit(OpCodes.Ldarg_0);		// rdr
il.Emit(OpCodes.Ldc_I4_0);		// rdr,0
il.Emit(OpCodes.Callvirt, fnIsDBNull);	// bool

The first operations are the start of the if-statement to see if the value is null or not. First the IDataReader is pushed onto the stack then Ldc_I4_0 pushes on an int 0. As ExecuteScalar always returns the first column of the first row, this 0 represents column(0).  The Callvirt calls a previously defined MethodInfo for IDataRow.IsDBNull(). These three statements are the equivalent of r.IsDbNull(0). The result of this method call will be left on the top of the stack.

var lblCont = il.DefineLabel();
il.Emit(OpCodes.Brfalse_S, lblCont);
il.Emit(OpCodes.Ldnull);		// null

If the value on the top of the stack is false (indicating that the value returned from the IDataReader is not null the execution is branched to a label further in the execution. If it is true then null is pushed onto the top of the stack.

var lblFin = il.DefineLabel();
il.Emit(OpCodes.Br_S, lblFin);
il.MarkLabel(lblCont);
il.Emit(OpCodes.Ldarg_0);		// rdr
il.Emit(OpCodes.Ldc_I4_0);		// rdr,0
il.Emit(OpCodes.Callvirt, fnGetValue);	// value

After the null has been pushed to the stack a Br_S jumps to the lblFin label, which we will see in a moment. We can now pick up the execution from the lblCont label. Recall that we would branch to this point if the value is not null. Once again the IDataReader is pushed to the stack, followed by 0. We have encountered fnGetValue previously. It is a MethodInfo for IDataRecord.GetValue(i). It will be called on the IDataReader with the paramerter 0. This will leave the value on the top of the stack.

il.MarkLabel(lblFin);
il.Emit(OpCodes.Unbox_Any, type);       // value converted

Finally Unbox_Any is called on the value. This converts the object value to the unboxed type specified. It has the effect of casting the value from the IDataReader to the specified type. If the incorrect type had been specified in the ExecuteScalar method an InvalidCastExcpetion would be called at this point.

As before the value would be returned by il.Emit(OpCodes.Ret).

POCOs

Now its time for the main event, as POCO support is how PetaPoco came to have its name and is one of the key differentiators from similar micro ORMs.  Lets jump straight in.  We have already encountered many of the op codes and MethodInfo’s so we can pick up the pace a little.

// var poco=new T()
il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance 
    | BindingFlags.Public | BindingFlags.NonPublic, nullnew Type[0], null));

// Enumerate all fields generating a set assignment for the column
for (int i = firstColumn; i < firstColumn + countColumns; i++)
{

The first thing is to push an instance of the the POCO type onto the stack with Newobj. Then its time to loop through the columns.

 // Get the PocoColumn for this db column, ignore if not known
 PocoColumn pc; if (!Columns.TryGetValue(r.GetName(i), out pc)) 
 continue;

PetaPoco is only interested in return columns from the IDataReader that are actually part of the POCO. Columns is a Dictionary holding the POCO property information. If there is not an entry in Columns for the current IDataReader column move onto the next.

 // Get the source type for this column
 var srcType = r.GetFieldType(i);
 var dstType = pc.PropertyInfo.PropertyType;

Next, get the source and destination types for the column.

il.Emit(OpCodes.Ldarg_0);		 // poco,rdr
il.Emit(OpCodes.Ldc_I4, i);		 // poco,rdr,i
il.Emit(OpCodes.Callvirt, fnIsDBNull);	 // poco,bool
var lblNext = il.DefineLabel();
il.Emit(OpCodes.Brtrue_S, lblNext);	 // poco

We have seen something very similar to this before, used to check if the IDataReader column is of type DBNull. If it is we branch to lblNext, which is directly before the end of the loop. This means that if the value is null in the database, no mapping to the POCO will take place.

il.Emit(OpCodes.Dup);			// poco,poco

var valuegetter = typeof(IDataRecord)
            .GetMethod("Get" + srcType.Name, new Type[] { typeof(int) });

Get a MethodInfo to call the required GetType method on the IDataReader.

if (valuegetter != null
    && valuegetter.ReturnType == srcType
    && (valuegetter.ReturnType == dstType 
        || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType)))
		{

Check if the return type from the valuegetter method is the same as the source type from the IDataReader, and that the return type is also the same as the underlying type. Nullable.GetUnderlyingType is used for the case where dstType is a Nullable type.

Once we have asserted that the mapping can take place we can start to build up the IL.

il.Emit(OpCodes.Ldarg_0);		// poco,poco,rdr
il.Emit(OpCodes.Ldc_I4, i);		// poco,poco,rdr,i
il.Emit(OpCodes.Callvirt, valuegetter);	// poco,poco,value

Get the value from the IDataReader using the MethodInfo defined above and put it on the top of the stack.

// Convert to Nullable
if (Nullable.GetUnderlyingType(dstType) != null)
{
    il.Emit(OpCodes.Newobj, 
    dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) }));
}

If the destination type (the POCO column) is a Nullable type, then convert the value from the IDataReader to the nullable version of the type. The code within the if-statement is equivalent to T? x = new T?(value). The stack is now poco, poco, value.

il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true));	// poco
il.MarkLabel(lblNext);
}

Finally, the property setter for the poco property is called with the value. The poco with the assigned value is left on the stack and the process is repeated with the next column.

When all the columns has been processed there is one final thing to do before we return the POCO.

var fnOnLoaded = RecurseInheritedTypes<MethodInfo>(type, 
        (x) => x.GetMethod("OnLoaded"BindingFlags.Instance | BindingFlags.Public 
            | BindingFlags.NonPublic, nullnew Type[0], null));

if (fnOnLoaded != null)
{
    il.Emit(OpCodes.Dup);
    il.Emit(OpCodes.Callvirt, fnOnLoaded);
}

Look for an OnLoaded method somwewhere in the POCO type inheritance hierarchy. If the method is present then call it on the POCO to signify that the POCO has been loaded.

il.Emit(OpCodes.Ret);

Finally return the POCO complete with the values from the IDataReader.

I would definately suggest having a look at the PetaPoco source. There are lots of interesting techniques to discover.

Advertisements

2 Responses to Dynamic Method Invocation in C# with PetaPoco (Part 2)

  1. Pingback: Dynamic Method Invocation in C# with PetaPoco (Part 1) « James Heppinstall: On Development

  2. Pingback: Fast serialization and deserialization using dynamically emitted POCOs | BlogoSfera

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: