Trace Dapper.NET Source Code - Fast Efficiency Key: Cache Principle
Tags: dotnet, csharp, dapper
Published: 2020-12-09 16:26:52

Github Link : [Trace\-Dapper.NET\-Source\-Code](https://github.com/shps951023/Trace-Dapper.NET-Source-Code)

----

## 10. One of the keys to Dapper's fast efficiency: Cache principle

Why can Dapper be so fast?  

I introduced the dynamic use of Emit IL to establish the ADO.NET Mapping method, but this function cannot make Dapper the king of lightweight ORM efficiency.

Because the dynamic create method is `Cost and time consuming` action, simply using it will slow down the speed. But when it cooperates with Cache, it is different. By storing the established method in Cache, you can use the `『Space for time』` concept to speed up the efficiency of the query .

Then trace the Dapper source code. This time, we need to pay special attention to Identity and GetCacheInfo under the QueryImpl method.

![image](https://user-images.githubusercontent.com/12729184/101129638-ef8f0f80-363c-11eb-8463-73a9ee081b1b.png)

**Identity、GetCacheInfo**

Identity mainly encapsulates the comparison Key attribute of each cache:

*   sql: distinguish different SQL strings
*   type: distinguish Mapping type
*   commandType: Responsible for distinguishing different databases
*   gridIndex: Mainly used in QueryMultiple, explained later.
*   connectionString: Mainly distinguish the same database manufacturer but different DB situation
*   parametersType: Mainly distinguish parameter types
*   typeCount: Mainly used in Multi Query multi\-mapping, it needs to be used with the override GetType method, which will be explained later

Then match the cache type used by Dapper in the GetCacheInfo method. When `ConcurrentDictionary<Identity, CacheInfo>` using the `TryGetValue` method, it will first compare the HashCode and then compare the Equals features, such as the image source code.

![image](https://user-images.githubusercontent.com/12729184/101129762-2ebd6080-363d-11eb-898c-8fa84b7205fa.png)

Using the Key type Identity to `override Equals` implement the cache comparison algorithm, you can see the following Dapper implementation logic. As long as one attribute is different, a new dynamic method and cache will be created.

```C#
public bool Equals(Identity other)
{
  if (ReferenceEquals(this, other)) return true;
  if (ReferenceEquals(other, null)) return false;

  int typeCount;
  return gridIndex == other.gridIndex
    && type == other.type
    && sql == other.sql
    && commandType == other.commandType
    && connectionStringComparer.Equals(connectionString, other.connectionString)
    && parametersType == other.parametersType
    && (typeCount = TypeCount) == other.TypeCount
    && (typeCount == 0 || TypesEqual(this, other, typeCount));
}
```

With this concept, the previous Emit version is modified into a simple Cache Demo :

```C#
public class Identity
{
	public string sql { get; set; }
	public CommandType? commandType { get; set; }
	public string connectionString { get; set; }
	public Type type { get; set; }
	public Type parametersType { get; set; }
	public Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType)
	{
		this.sql = sql;
		this.commandType = commandType;
		this.connectionString = connectionString;
		this.type = type;
		this.parametersType = parametersType;
		unchecked
		{
			hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this
			hashCode = (hashCode * 23) + commandType.GetHashCode();
			hashCode = (hashCode * 23) + (sql?.GetHashCode() ?? 0);
			hashCode = (hashCode * 23) + (type?.GetHashCode() ?? 0);
			hashCode = (hashCode * 23) + (connectionString == null ? 0 : StringComparer.Ordinal.GetHashCode(connectionString));
			hashCode = (hashCode * 23) + (parametersType?.GetHashCode() ?? 0);
		}
	}

	public readonly int hashCode;
	public override int GetHashCode() => hashCode;

	public override bool Equals(object obj) => Equals(obj as Identity);
	public bool Equals(Identity other)
	{
		if (ReferenceEquals(this, other)) return true;
		if (ReferenceEquals(other, null)) return false;

		return type == other.type
		  && sql == other.sql
		  && commandType == other.commandType
		  && StringComparer.Ordinal.Equals(connectionString, other.connectionString)
		  && parametersType == other.parametersType;
	}
}

public static class DemoExtension
{
	private static readonly Dictionary<Identity, Func<DbDataReader, object>> readers = new Dictionary<Identity, Func<DbDataReader, object>>();

	public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null) where T : new()
	{
		using (var command = cnn.CreateCommand())
		{
			command.CommandText = sql;
			using (var reader = command.ExecuteReader())
			{
				var identity = new Identity(command.CommandText, command.CommandType, cnn.ConnectionString, typeof(T), param?.GetType());

				// 2. If the cache has data, use it, and if there is no data, create a method dynamically and save it in the cache 
				if (!readers.TryGetValue(identity, out Func<DbDataReader, object> func))
				{
					//The dynamic creation method 
					func = GetTypeDeserializerImpl(typeof(T), reader);
					readers[identity] = func;
					Console.WriteLine(" No cache, create a dynamic method and put it in the cache ");
				}
				else
				{
					Console.WriteLine(" Use cache ");
				}


				// 3. Call the generated method by reader, read the data and return 
				while (reader.Read())
				{
					var result = func(reader as DbDataReader);
					yield return result is T ? (T)result : default(T);
				}
			}

		}
	}

	private static Func<DbDataReader, object> GetTypeDeserializerImpl(Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false)
	{
		// ..
	}
}
```

![image](https://user-images.githubusercontent.com/12729184/101130013-a5f2f480-363d-11eb-8e1d-1b9851c6e5b2.png)