From 87bdbcff6262c2db510722606070c1e4dddaca07 Mon Sep 17 00:00:00 2001 From: Gavin Hills Date: Mon, 5 Dec 2022 08:41:48 -0800 Subject: [PATCH 1/2] Escape id column name in Get, GetAsync Get\GetAsync do not work correctly with SQL Server when the Id column is a reserved keyword (e.g. "Key") marked with the `ExplicitKeyAttribute`. Use the adapter specific column name escaping to resolve the issue. --- Dapper.sln | 2 +- src/Dapper.Contrib/SqlMapperExtensions.Async.cs | 7 ++++++- src/Dapper.Contrib/SqlMapperExtensions.cs | 7 ++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Dapper.sln b/Dapper.sln index e993c7a4..4aa75f10 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -16,7 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution version.json = version.json EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Contrib", "Dapper.Contrib\Dapper.Contrib.csproj", "{4E409F8F-CFBB-4332-8B0A-FD5A283051FD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Contrib", "src\Dapper.Contrib\Dapper.Contrib.csproj", "{4E409F8F-CFBB-4332-8B0A-FD5A283051FD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Contrib", "tests\Dapper.Tests.Contrib\Dapper.Tests.Contrib.csproj", "{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}" EndProject diff --git a/src/Dapper.Contrib/SqlMapperExtensions.Async.cs b/src/Dapper.Contrib/SqlMapperExtensions.Async.cs index c93e39a4..d7cea7fa 100644 --- a/src/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/src/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -30,7 +30,12 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i var key = GetSingleKey(nameof(GetAsync)); var name = GetTableName(type); - sql = $"SELECT * FROM {name} WHERE {key.Name} = @id"; + var adapter = GetFormatter(connection); + + var sbGetQuery = new StringBuilder($"SELECT * FROM {name} WHERE "); + adapter.AppendColumnName(sbGetQuery, key.Name); + sbGetQuery.Append(" = @id"); + sql = sbGetQuery.ToString(); GetQueries[type.TypeHandle] = sql; } diff --git a/src/Dapper.Contrib/SqlMapperExtensions.cs b/src/Dapper.Contrib/SqlMapperExtensions.cs index 9a30e805..42780a8f 100644 --- a/src/Dapper.Contrib/SqlMapperExtensions.cs +++ b/src/Dapper.Contrib/SqlMapperExtensions.cs @@ -176,7 +176,12 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction var key = GetSingleKey(nameof(Get)); var name = GetTableName(type); - sql = $"select * from {name} where {key.Name} = @id"; + var adapter = GetFormatter(connection); + + var sbGetQuery = new StringBuilder($"SELECT * FROM {name} WHERE "); + adapter.AppendColumnName(sbGetQuery, key.Name); + sbGetQuery.Append(" = @id"); + sql = sbGetQuery.ToString(); GetQueries[type.TypeHandle] = sql; } From 500a1887770d41707ac885544d4ea6f43da572fc Mon Sep 17 00:00:00 2001 From: Gavin Hills Date: Mon, 5 Dec 2022 09:43:44 -0800 Subject: [PATCH 2/2] Scope get query caching to the connection type get query caching uses connection type specific formatting so the cache needs to be appropriately scoped in cases of concurrent usage with different connection types. Also split out query for GetAll queries to distinguish those. --- src/Dapper.Contrib/SqlMapperExtensions.Async.cs | 12 ++++++++---- src/Dapper.Contrib/SqlMapperExtensions.cs | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Dapper.Contrib/SqlMapperExtensions.Async.cs b/src/Dapper.Contrib/SqlMapperExtensions.Async.cs index d7cea7fa..508b84af 100644 --- a/src/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/src/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -25,7 +25,11 @@ public static partial class SqlMapperExtensions public static async Task GetAsync(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); - if (!GetQueries.TryGetValue(type.TypeHandle, out string sql)) + + // get query caching depends on connection specific formatting + var typeTuple = Tuple.Create(connection.GetType().TypeHandle, type.TypeHandle); + + if (!GetQueries.TryGetValue(typeTuple, out string sql)) { var key = GetSingleKey(nameof(GetAsync)); var name = GetTableName(type); @@ -36,7 +40,7 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i adapter.AppendColumnName(sbGetQuery, key.Name); sbGetQuery.Append(" = @id"); sql = sbGetQuery.ToString(); - GetQueries[type.TypeHandle] = sql; + GetQueries[typeTuple] = sql; } var dynParams = new DynamicParameters(); @@ -88,13 +92,13 @@ public static Task> GetAllAsync(this IDbConnection connection, var type = typeof(T); var cacheType = typeof(List); - if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql)) + if (!GetAllQueries.TryGetValue(cacheType.TypeHandle, out string sql)) { GetSingleKey(nameof(GetAll)); var name = GetTableName(type); sql = "SELECT * FROM " + name; - GetQueries[cacheType.TypeHandle] = sql; + GetAllQueries[cacheType.TypeHandle] = sql; } if (!type.IsInterface) diff --git a/src/Dapper.Contrib/SqlMapperExtensions.cs b/src/Dapper.Contrib/SqlMapperExtensions.cs index 42780a8f..fc20c27a 100644 --- a/src/Dapper.Contrib/SqlMapperExtensions.cs +++ b/src/Dapper.Contrib/SqlMapperExtensions.cs @@ -56,7 +56,11 @@ public interface ITableNameMapper private static readonly ConcurrentDictionary> ExplicitKeyProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary GetAllQueries = new ConcurrentDictionary(); + /// + /// Key is a composite of connection type, entity type + /// + private static readonly ConcurrentDictionary, string> GetQueries = new ConcurrentDictionary, string>(); private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter(); @@ -171,7 +175,10 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction { var type = typeof(T); - if (!GetQueries.TryGetValue(type.TypeHandle, out string sql)) + // get query caching depends on connection specific formatting + var typeTuple = Tuple.Create(connection.GetType().TypeHandle, type.TypeHandle); + + if (!GetQueries.TryGetValue(typeTuple, out string sql)) { var key = GetSingleKey(nameof(Get)); var name = GetTableName(type); @@ -182,7 +189,7 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction adapter.AppendColumnName(sbGetQuery, key.Name); sbGetQuery.Append(" = @id"); sql = sbGetQuery.ToString(); - GetQueries[type.TypeHandle] = sql; + GetQueries[typeTuple] = sql; } var dynParams = new DynamicParameters(); @@ -239,13 +246,13 @@ public static IEnumerable GetAll(this IDbConnection connection, IDbTransac var type = typeof(T); var cacheType = typeof(List); - if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql)) + if (!GetAllQueries.TryGetValue(cacheType.TypeHandle, out string sql)) { GetSingleKey(nameof(GetAll)); var name = GetTableName(type); sql = "select * from " + name; - GetQueries[cacheType.TypeHandle] = sql; + GetAllQueries[cacheType.TypeHandle] = sql; } if (!type.IsInterface) return connection.Query(sql, null, transaction, commandTimeout: commandTimeout);