Skip to content

Commit f477f7e

Browse files
yinzararoji
authored andcommitted
Keep parameter values out IMemoryCache in RelationalCommandCache
Store only nullness and array lengths in struct form to prevent parameters memory leaks Fixes dotnet#34028 Co-authored-by: Shay Rojansky <[email protected]> (cherry picked from commit af420cd)
1 parent f9b5a38 commit f477f7e

File tree

1 file changed

+23
-20
lines changed

1 file changed

+23
-20
lines changed

src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections;
54
using System.Collections.Concurrent;
65
using Microsoft.Extensions.Caching.Memory;
76

@@ -106,12 +105,21 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter)
106105
private readonly struct CommandCacheKey : IEquatable<CommandCacheKey>
107106
{
108107
private readonly Expression _queryExpression;
109-
private readonly IReadOnlyDictionary<string, object?> _parameterValues;
108+
private readonly Dictionary<string, ParameterInfo> _parameterInfos;
110109

111-
public CommandCacheKey(Expression queryExpression, IReadOnlyDictionary<string, object?> parameterValues)
110+
internal CommandCacheKey(Expression queryExpression, IReadOnlyDictionary<string, object?> parameterValues)
112111
{
113112
_queryExpression = queryExpression;
114-
_parameterValues = parameterValues;
113+
_parameterInfos = new Dictionary<string, ParameterInfo>();
114+
115+
foreach (var (key, value) in parameterValues)
116+
{
117+
_parameterInfos[key] = new ParameterInfo
118+
{
119+
IsNull = value is null,
120+
ObjectArrayLength = value is object[] arr ? arr.Length : null
121+
};
122+
}
115123
}
116124

117125
public override bool Equals(object? obj)
@@ -126,27 +134,18 @@ public bool Equals(CommandCacheKey commandCacheKey)
126134
return false;
127135
}
128136

129-
if (_parameterValues.Count > 0)
137+
Check.DebugAssert(
138+
_parameterInfos.Count == commandCacheKey._parameterInfos.Count,
139+
"Parameter Count mismatch between identical queries");
140+
141+
if (_parameterInfos.Count > 0)
130142
{
131-
foreach (var (key, value) in _parameterValues)
143+
foreach (var (key, info) in _parameterInfos)
132144
{
133-
if (!commandCacheKey._parameterValues.TryGetValue(key, out var otherValue))
145+
if (!commandCacheKey._parameterInfos.TryGetValue(key, out var otherInfo) || info != otherInfo)
134146
{
135147
return false;
136148
}
137-
138-
// ReSharper disable once ArrangeRedundantParentheses
139-
if ((value == null) != (otherValue == null))
140-
{
141-
return false;
142-
}
143-
144-
if (value is IEnumerable
145-
&& value.GetType() == typeof(object[]))
146-
{
147-
// FromSql parameters must have the same number of elements
148-
return ((object[])value).Length == (otherValue as object[])?.Length;
149-
}
150149
}
151150
}
152151

@@ -156,4 +155,8 @@ public bool Equals(CommandCacheKey commandCacheKey)
156155
public override int GetHashCode()
157156
=> 0;
158157
}
158+
159+
// Note that we keep only the null-ness of parameters (and array length for FromSql object arrays),
160+
// and avoid referencing the actual parameter data (see #34028).
161+
private readonly record struct ParameterInfo(bool IsNull, int? ObjectArrayLength);
159162
}

0 commit comments

Comments
 (0)