Skip to content

Commit 38f72ca

Browse files
rojiyinzara
andauthored
[release/8.0] Keep parameter values out IMemoryCache in RelationalCommandCache (#34908)
Store only nullness and array lengths in struct form to prevent parameters memory leaks Fixes #34028 Co-authored-by: Matthew Vance <[email protected]>
1 parent f9b5a38 commit 38f72ca

File tree

1 file changed

+60
-17
lines changed

1 file changed

+60
-17
lines changed

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

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,35 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter)
105105

106106
private readonly struct CommandCacheKey : IEquatable<CommandCacheKey>
107107
{
108+
private static readonly bool UseOldBehavior34201 =
109+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue34028", out var enabled34028) && enabled34028;
110+
108111
private readonly Expression _queryExpression;
109-
private readonly IReadOnlyDictionary<string, object?> _parameterValues;
112+
private readonly Dictionary<string, ParameterInfo> _parameterInfos;
113+
114+
// Quirk only
115+
private readonly IReadOnlyDictionary<string, object?>? _parameterValues;
110116

111117
public CommandCacheKey(Expression queryExpression, IReadOnlyDictionary<string, object?> parameterValues)
112118
{
113119
_queryExpression = queryExpression;
114-
_parameterValues = parameterValues;
120+
_parameterInfos = new Dictionary<string, ParameterInfo>();
121+
122+
if (UseOldBehavior34201)
123+
{
124+
_parameterValues = parameterValues;
125+
}
126+
else
127+
{
128+
foreach (var (key, value) in parameterValues)
129+
{
130+
_parameterInfos[key] = new ParameterInfo
131+
{
132+
IsNull = value is null,
133+
ObjectArrayLength = value is object[] arr ? arr.Length : null
134+
};
135+
}
136+
}
115137
}
116138

117139
public override bool Equals(object? obj)
@@ -126,26 +148,43 @@ public bool Equals(CommandCacheKey commandCacheKey)
126148
return false;
127149
}
128150

129-
if (_parameterValues.Count > 0)
151+
Check.DebugAssert(
152+
_parameterInfos.Count == commandCacheKey._parameterInfos.Count,
153+
"Parameter Count mismatch between identical queries");
154+
155+
if (_parameterInfos.Count > 0)
130156
{
131-
foreach (var (key, value) in _parameterValues)
157+
if (UseOldBehavior34201)
132158
{
133-
if (!commandCacheKey._parameterValues.TryGetValue(key, out var otherValue))
134-
{
135-
return false;
136-
}
137-
138-
// ReSharper disable once ArrangeRedundantParentheses
139-
if ((value == null) != (otherValue == null))
159+
foreach (var (key, value) in _parameterValues!)
140160
{
141-
return false;
161+
if (!_parameterValues.TryGetValue(key, out var otherValue))
162+
{
163+
return false;
164+
}
165+
166+
// ReSharper disable once ArrangeRedundantParentheses
167+
if ((value == null) != (otherValue == null))
168+
{
169+
return false;
170+
}
171+
172+
if (value is IEnumerable
173+
&& value.GetType() == typeof(object[]))
174+
{
175+
// FromSql parameters must have the same number of elements
176+
return ((object[])value).Length == (otherValue as object[])?.Length;
177+
}
142178
}
143-
144-
if (value is IEnumerable
145-
&& value.GetType() == typeof(object[]))
179+
}
180+
else
181+
{
182+
foreach (var (key, info) in _parameterInfos)
146183
{
147-
// FromSql parameters must have the same number of elements
148-
return ((object[])value).Length == (otherValue as object[])?.Length;
184+
if (!commandCacheKey._parameterInfos.TryGetValue(key, out var otherInfo) || info != otherInfo)
185+
{
186+
return false;
187+
}
149188
}
150189
}
151190
}
@@ -156,4 +195,8 @@ public bool Equals(CommandCacheKey commandCacheKey)
156195
public override int GetHashCode()
157196
=> 0;
158197
}
198+
199+
// Note that we keep only the null-ness of parameters (and array length for FromSql object arrays),
200+
// and avoid referencing the actual parameter data (see #34028).
201+
private readonly record struct ParameterInfo(bool IsNull, int? ObjectArrayLength);
159202
}

0 commit comments

Comments
 (0)