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
- Clone the latest version from Dapper's Github
- Create .Net Core Console project
- Install the NuGet SqlClient and add the Dapper Project Reference.
- 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
- The entity class is actually
DapperRow
that transformed implicitly to dynamic. - 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:
- Create a
dynamic
type variable, the entity type isExpandoObject
. - Because there’s an inheritance relationship that can be transformed to
IDictionary<string, object>
- 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 - 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;
}
}
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);
}
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:
- 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.
- 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;
}
- 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));
}
- 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);
}
}
- DapperRowMetaObject main function is to define behavior, by override
BindSetMember、BindGetMember
method, Dapper defines Get, Set of behavior were usedIDictionary<string, object> - GetItem
,DapperRow - SetValue
- 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{/*...*/}
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{/*..*/}
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>
}
Top comments (0)