Github Link : 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.
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.
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.
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 :
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)
{
// ..
}
}
Top comments (0)