DEV Community

Wei
Wei

Posted on • Edited on

Trace Dapper.NET Source Code - Introduction、Dynamic Query

Github Link : https://github.com/shps951023/Trace-Dapper.NET-Source-Code


1. Introduction

After years of promotion by Industry Veterans and StackOverflow, “Dapper with Entity Framework” is a powerful combination that deal the needs of “safe, convenient, efficient, maintainable”.

But the current network articles, although there are many articles on Dapper but stay on how to use, no one systematic explanation of the source code logic. So with this article “Trace Dapper Source Code” want to take you into the Dapper code, to understand the details of the design, efficient principles, and learn up practical application in the work.

2. Installation Environment

  1. Clone the latest version from Dapper's Github
  2. Create .Net Core Console project
  3. Install the NuGet SqlClient and add the Dapper Project Reference.
  4. Running console with breakpoint it allows runtime to view the logic.

My Personal Environment

  • MSSQLLOCALDB
  • Visaul Studio 2019
  • LINQPad 5
  • Dapper version: V2.0.30
  • ILSpy
  • Windows 10 pro

3. Dynamic Query

With Dapper dynamic Query, you can save time in modifying class attributes in the early stages of development because the table structure is still in the adjustment stage, or it isn’t worth the extra effort to declare class lightweight requirements.

When the table is stable, use the POCO generator to quickly generate the Class and convert it to strong type maintenance, e.g PocoClassGenerator..

Why can Dapper be so convenient and support dynamic?

Two key points can be found by tracing the source code of the Query method

  1. The entity class is actually DapperRow that transformed implicitly to dynamic.
  2. DapperRow inherits IDynamicMetaObjectProviderand & implements corresponding methods.

For this logic, I will make a simplified version of Dapper dynamic Query to let readers understand the conversion logic:

  1. Create a dynamic type variable, the entity type is ExpandoObject.
  2. Because there’s an inheritance relationship that can be transformed to IDictionary<string, object>
  3. Use DataReader to get the field name using GetName, get the value from the field index, and add both to the Dictionary as a key and value
  4. Because expandobject has the implementation IDynamicMetaObjectProvider interface that can be converted to dynamic


public static class DemoExtension
{
  public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql)
  {
    using (var command = cnn.CreateCommand())
    {
      command.CommandText = sql;
      using (var reader = command.ExecuteReader())
      {
        while (reader.Read())
        {
          yield return reader.CastToDynamic();
        }
      }
    }
  }

  private static dynamic CastToDynamic(this IDataReader reader)
  {
    dynamic e = new ExpandoObject();
    var d = e as IDictionary<string,object>;
    for (int i = 0; i < reader.FieldCount; i++)
      d.Add(reader.GetName(i),reader[i]);
    return e;
  }
}


Enter fullscreen mode Exit fullscreen mode

Now that we have the concept of the simple expandobject Dynamic Query example, go to the deep level to see how Dapper handles the details and why dapper customize the DynamicMetaObjectProvider.

First, learn the Dynamic Query process logic:
code:



using (var cn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDB;Integrated Security=SSPI;Initial Catalog=master;"))
{
    var result = cn.Query("select N'Wei' Name,26 Age").First();
    Console.WriteLine(result.Name);
}


Enter fullscreen mode Exit fullscreen mode

The value of the process would be:
Create Dynamic FUNC > stored in the cache > use result.Name > transfer to call ((DapperRow)result)["Name"] > from DapperTable.Values Array with index value corresponding to the field "Name" in the Values array to get value.

Then look at the source code of the GetDapperRowDeserializer method, which controls the logic of how dynamic runs, and is dynamically created as Func for upper-level API calls and cache reuse.

This section of Func logic:

  1. Although DapperTable is a local variable in the method, it is referenced by the generated Func, so it will not be GC and always stored in the memory and reused.
  2. Because it is dynamic, there is no need to consider the type Mapping, here directly use GetValue(index) to get value from database.


var values = new object[select columns count];
for (int i = 0; i < values.Length; i++)
{
    object val = r.GetValue(i);
    values[i] = val is DBNull ? null : val;
}


Enter fullscreen mode Exit fullscreen mode
  1. Save the data in DapperRow


public DapperRow(DapperTable table, object[] values)
{
    this.table = table ?? throw new ArgumentNullException(nameof(table));
    this.values = values ?? throw new ArgumentNullException(nameof(values));
}


Enter fullscreen mode Exit fullscreen mode
  1. DapperRow inherits IDynamicMetaObjectProvider and implements the GetMetaObject method. The implementation logic is to return the DapperRowMetaObject object.


private sealed partial class DapperRow : System.Dynamic.IDynamicMetaObjectProvider
{
    DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this);
    }
}


Enter fullscreen mode Exit fullscreen mode
  1. DapperRowMetaObject main function is to define behavior, by override BindSetMember、BindGetMember method, Dapper defines Get, Set of behavior were used IDictionary<string, object> - GetItem , DapperRow - SetValue
  2. Finally, Dapper uses the DataReader column order , first using the column name to get Index, then using Index and Values.

Why inherit IDictionary?

There is a question to think about: In DapperRowMetaObject, you can define the Get and Set behaviors by yourself, so instead of using the Dictionary-GetItem method, instead of using other methods, does it mean that you don't need to inherit IDictionary<string,object>?

One of the reasons for Dapper to do this is related to the open principle. DapperTable and DapperRow are all low-level implementation class. Based on the open and closed principle, they should not be opened to users, so they are set as private.



private class DapperTable{/*...*/}
private class DapperRow :IDictionary<string, object>, IReadOnlyDictionary<string, object>,System.Dynamic.IDynamicMetaObjectProvider{/*...*/}


Enter fullscreen mode Exit fullscreen mode

What if the user wants to know the field name?

Because DapperRow implements IDictionary, it can be upcasting to IDictionary<string, object>, and use it to get field data by public interface.



public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable{/*..*/}


Enter fullscreen mode Exit fullscreen mode

For example, I’ve created a tool called HtmlTableHelper to use this feature to automatically convert Dapper Dynamic Query to Table Html, such as the following code and picture



using (var cn = "Your Connection")
{
  var sourceData = cn.Query(@"select 'ITWeiHan' Name,25 Age,'M' Gender");
  var tablehtml = sourceData.ToHtmlTable(); //Result : <table><thead><tr><th>Name</th><th>Age</th><th>Gender</th></tr></thead><tbody><tr><td>ITWeiHan</td><td>25</td><td>M</td></tr></tbody></table>
}


Enter fullscreen mode Exit fullscreen mode

Top comments (0)