Skip to content

Commit af420cd

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

File tree

1 file changed

+29
-21
lines changed

1 file changed

+29
-21
lines changed

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

Lines changed: 29 additions & 21 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 System.Runtime.CompilerServices;
76
using Microsoft.Extensions.Caching.Memory;
@@ -106,11 +105,25 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter)
106105
}
107106
}
108107

109-
private readonly struct CommandCacheKey(Expression queryExpression, IReadOnlyDictionary<string, object?> parameterValues)
110-
: IEquatable<CommandCacheKey>
108+
private readonly struct CommandCacheKey : IEquatable<CommandCacheKey>
111109
{
112-
private readonly Expression _queryExpression = queryExpression;
113-
private readonly IReadOnlyDictionary<string, object?> _parameterValues = parameterValues;
110+
private readonly Expression _queryExpression;
111+
private readonly Dictionary<string, ParameterInfo> _parameterInfos;
112+
113+
internal CommandCacheKey(Expression queryExpression, IReadOnlyDictionary<string, object?> parameterValues)
114+
{
115+
_queryExpression = queryExpression;
116+
_parameterInfos = new Dictionary<string, ParameterInfo>();
117+
118+
foreach (var (key, value) in parameterValues)
119+
{
120+
_parameterInfos[key] = new ParameterInfo
121+
{
122+
IsNull = value is null,
123+
ObjectArrayLength = value is object[] arr ? arr.Length : null
124+
};
125+
}
126+
}
114127

115128
public override bool Equals(object? obj)
116129
=> obj is CommandCacheKey commandCacheKey
@@ -124,27 +137,18 @@ public bool Equals(CommandCacheKey commandCacheKey)
124137
return false;
125138
}
126139

127-
if (_parameterValues.Count > 0)
140+
Check.DebugAssert(
141+
_parameterInfos.Count == commandCacheKey._parameterInfos.Count,
142+
"Parameter Count mismatch between identical queries");
143+
144+
if (_parameterInfos.Count > 0)
128145
{
129-
foreach (var (key, value) in _parameterValues)
146+
foreach (var (key, info) in _parameterInfos)
130147
{
131-
if (!commandCacheKey._parameterValues.TryGetValue(key, out var otherValue))
148+
if (!commandCacheKey._parameterInfos.TryGetValue(key, out var otherInfo) || info != otherInfo)
132149
{
133150
return false;
134151
}
135-
136-
// ReSharper disable once ArrangeRedundantParentheses
137-
if ((value == null) != (otherValue == null))
138-
{
139-
return false;
140-
}
141-
142-
if (value is IEnumerable
143-
&& value.GetType() == typeof(object[]))
144-
{
145-
// FromSql parameters must have the same number of elements
146-
return ((object[])value).Length == (otherValue as object[])?.Length;
147-
}
148152
}
149153
}
150154

@@ -154,4 +158,8 @@ public bool Equals(CommandCacheKey commandCacheKey)
154158
public override int GetHashCode()
155159
=> RuntimeHelpers.GetHashCode(_queryExpression);
156160
}
161+
162+
// Note that we keep only the null-ness of parameters (and array length for FromSql object arrays),
163+
// and avoid referencing the actual parameter data (see #34028).
164+
private readonly record struct ParameterInfo(bool IsNull, int? ObjectArrayLength);
157165
}

0 commit comments

Comments
 (0)