diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs
index 219324d0882..425a490e0dc 100644
--- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs
+++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
using System.Collections;
using System.Globalization;
using System.Numerics;
@@ -9,6 +8,8 @@
using System.Security;
using System.Text;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;
@@ -27,7 +28,7 @@ public class CSharpHelper : ICSharpHelper
{
private readonly ITypeMappingSource _typeMappingSource;
private readonly Project _project;
- private readonly LinqToCSharpSyntaxTranslator _translator;
+ private readonly RuntimeModelLinqToCSharpSyntaxTranslator _translator;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -45,11 +46,11 @@ public CSharpHelper(ITypeMappingSource typeMappingSource)
var projectInfo = ProjectInfo.Create(projectId, versionStamp, "Proj", "Proj", LanguageNames.CSharp);
_project = workspace.AddProject(projectInfo);
var syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, LanguageNames.CSharp);
- _translator = new LinqToCSharpSyntaxTranslator(syntaxGenerator);
+ _translator = new RuntimeModelLinqToCSharpSyntaxTranslator(syntaxGenerator);
}
- private static readonly IReadOnlyCollection Keywords = new[]
- {
+ private static readonly IReadOnlyCollection Keywords =
+ [
"__arglist",
"__makeref",
"__reftype",
@@ -131,7 +132,7 @@ public CSharpHelper(ITypeMappingSource typeMappingSource)
"void",
"volatile",
"while"
- };
+ ];
private static readonly IReadOnlyDictionary> LiteralFuncs =
new Dictionary>
@@ -1435,13 +1436,13 @@ public virtual string Fragment(AttributeCodeFragment fragment)
}
builder
- .Append("[")
+ .Append('[')
.Append(attributeName);
if (fragment.Arguments.Count != 0
|| fragment.NamedArguments.Count != 0)
{
- builder.Append("(");
+ builder.Append('(');
var first = true;
foreach (var value in fragment.Arguments)
@@ -1475,10 +1476,10 @@ public virtual string Fragment(AttributeCodeFragment fragment)
.Append(UnknownLiteral(item.Value));
}
- builder.Append(")");
+ builder.Append(')');
}
- builder.Append("]");
+ builder.Append(']');
return builder.ToString();
}
@@ -1552,8 +1553,40 @@ private string ToSourceCode(SyntaxNode node)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual string Statement(Expression node, ISet collectedNamespaces)
- => ToSourceCode(_translator.TranslateStatement(node, collectedNamespaces));
+ public virtual string Statement(
+ Expression node,
+ Dictionary? constantReplacements,
+ Dictionary? memberAccessReplacements,
+ ISet collectedNamespaces)
+ {
+ Dictionary? constantReplacementExpressions = null;
+ if (constantReplacements != null)
+ {
+ constantReplacementExpressions = [];
+
+ foreach (var instancePair in constantReplacements)
+ {
+ constantReplacementExpressions[instancePair.Key] = SyntaxFactory.IdentifierName(instancePair.Value);
+ }
+ }
+
+ Dictionary? memberAccessReplacementExpressions = null;
+ if (memberAccessReplacements != null)
+ {
+ memberAccessReplacementExpressions = [];
+
+ foreach (var methodPair in memberAccessReplacements)
+ {
+ memberAccessReplacementExpressions[methodPair.Key] = SyntaxFactory.IdentifierName(methodPair.Value);
+ }
+ }
+
+ return ToSourceCode(_translator.TranslateStatement(
+ node,
+ constantReplacementExpressions,
+ memberAccessReplacementExpressions,
+ collectedNamespaces));
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -1561,8 +1594,40 @@ public virtual string Statement(Expression node, ISet collectedNamespace
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual string Expression(Expression node, ISet collectedNamespaces)
- => ToSourceCode(_translator.TranslateExpression(node, collectedNamespaces));
+ public virtual string Expression(
+ Expression node,
+ Dictionary? constantReplacements,
+ Dictionary? memberAccessReplacements,
+ ISet collectedNamespaces)
+ {
+ Dictionary? constantReplacementExpressions = null;
+ if (constantReplacements != null)
+ {
+ constantReplacementExpressions = [];
+
+ foreach (var instancePair in constantReplacements)
+ {
+ constantReplacementExpressions[instancePair.Key] = SyntaxFactory.IdentifierName(instancePair.Value);
+ }
+ }
+
+ Dictionary? memberAccessReplacementExpressions = null;
+ if (memberAccessReplacements != null)
+ {
+ memberAccessReplacementExpressions = [];
+
+ foreach (var methodPair in memberAccessReplacements)
+ {
+ memberAccessReplacementExpressions[methodPair.Key] = SyntaxFactory.IdentifierName(methodPair.Value);
+ }
+ }
+
+ return ToSourceCode(_translator.TranslateExpression(
+ node,
+ constantReplacementExpressions,
+ memberAccessReplacementExpressions,
+ collectedNamespaces));
+ }
private static bool IsIdentifierStartCharacter(char ch)
{
diff --git a/src/EFCore.Design/Query/Internal/ILinqToCSharpSyntaxTranslator.cs b/src/EFCore.Design/Query/Internal/ILinqToCSharpSyntaxTranslator.cs
deleted file mode 100644
index 50f6a28061d..00000000000
--- a/src/EFCore.Design/Query/Internal/ILinqToCSharpSyntaxTranslator.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.CodeAnalysis;
-
-namespace Microsoft.EntityFrameworkCore.Query.Internal;
-
-///
-/// Translates a LINQ expression tree to a Roslyn syntax tree.
-///
-///
-/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
-/// the same compatibility standards as public APIs. It may be changed or removed without notice in
-/// any release. You should only use it directly in your code with extreme caution and knowing that
-/// doing so can result in application failures when updating to a new Entity Framework Core release.
-///
-public interface ILinqToCSharpSyntaxTranslator
-{
- ///
- /// Translates a node representing a statement into a Roslyn syntax tree.
- ///
- /// The node to be translated.
- /// Any namespaces required by the translated code will be added to this set.
- /// A Roslyn syntax tree representation of .
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- SyntaxNode TranslateStatement(Expression node, ISet collectedNamespaces);
-
- ///
- /// Translates a node representing an expression into a Roslyn syntax tree.
- ///
- /// The node to be translated.
- /// Any namespaces required by the translated code will be added to this set.
- /// A Roslyn syntax tree representation of .
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- SyntaxNode TranslateExpression(Expression node, ISet collectedNamespaces);
-
- ///
- /// Returns the captured variables detected in the last translation.
- ///
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- IReadOnlySet CapturedVariables { get; }
-}
diff --git a/src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs b/src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs
index 8cfcc69b839..b46174daa09 100644
--- a/src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs
+++ b/src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs
@@ -4,15 +4,16 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
+using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
-using ConditionalExpression = System.Linq.Expressions.ConditionalExpression;
using E = System.Linq.Expressions.Expression;
namespace Microsoft.EntityFrameworkCore.Query.Internal;
@@ -23,7 +24,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
-public class LinqToCSharpSyntaxTranslator : ExpressionVisitor, ILinqToCSharpSyntaxTranslator
+public class LinqToCSharpSyntaxTranslator : ExpressionVisitor
{
private sealed record StackFrame(
Dictionary Variables,
@@ -32,13 +33,7 @@ private sealed record StackFrame(
HashSet UnnamedLabelNames);
private readonly Stack _stack
- = new(
- new[]
- {
- new StackFrame(
- new Dictionary(), [], new Dictionary(),
- [])
- });
+ = new([new StackFrame([], [], [], [])]);
private int _unnamedParameterCounter;
@@ -51,14 +46,13 @@ private sealed record LiftedState(
private LiftedState _liftedState = new([], new Dictionary(), [], []);
private ExpressionContext _context;
+ private Dictionary? _constantReplacements;
private bool _onLastLambdaLine;
private readonly HashSet _capturedVariables = [];
private ISet _collectedNamespaces = null!;
private static MethodInfo? _activatorCreateInstanceMethod;
- private static MethodInfo? _typeGetFieldMethod;
- private static MethodInfo? _fieldGetValueMethod;
private static MethodInfo? _mathPowMethod;
private readonly SideEffectDetectionSyntaxWalker _sideEffectDetector = new();
@@ -82,7 +76,7 @@ public LinqToCSharpSyntaxTranslator(SyntaxGenerator syntaxGenerator)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public IReadOnlySet CapturedVariables
+ public virtual IReadOnlySet CapturedVariables
=> _capturedVariables.ToHashSet();
///
@@ -99,8 +93,11 @@ public IReadOnlySet CapturedVariables
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual SyntaxNode TranslateStatement(Expression node, ISet collectedNamespaces)
- => TranslateCore(node, collectedNamespaces, statementContext: true);
+ public virtual SyntaxNode TranslateStatement(
+ Expression node,
+ Dictionary? constantReplacements,
+ ISet collectedNamespaces)
+ => TranslateCore(node, constantReplacements, collectedNamespaces, statementContext: true);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -108,8 +105,11 @@ public virtual SyntaxNode TranslateStatement(Expression node, ISet colle
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual SyntaxNode TranslateExpression(Expression node, ISet collectedNamespaces)
- => TranslateCore(node, collectedNamespaces, statementContext: false);
+ public virtual SyntaxNode TranslateExpression(
+ Expression node,
+ Dictionary? constantReplacements,
+ ISet collectedNamespaces)
+ => TranslateCore(node, constantReplacements, collectedNamespaces, statementContext: false);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -117,9 +117,14 @@ public virtual SyntaxNode TranslateExpression(Expression node, ISet coll
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- protected virtual SyntaxNode TranslateCore(Expression node, ISet collectedNamespaces, bool statementContext = false)
+ protected virtual SyntaxNode TranslateCore(
+ Expression node,
+ Dictionary? constantReplacements,
+ ISet collectedNamespaces,
+ bool statementContext)
{
_capturedVariables.Clear();
+ _constantReplacements = constantReplacements;
_collectedNamespaces = collectedNamespaces;
_unnamedParameterCounter = 0;
_context = statementContext ? ExpressionContext.Statement : ExpressionContext.Expression;
@@ -127,12 +132,10 @@ protected virtual SyntaxNode TranslateCore(Expression node, ISet collect
Visit(node);
- if (_liftedState.Statements.Count > 0)
+ if (_liftedState.Statements.Count > 0
+ && _context == ExpressionContext.Expression)
{
- if (_context == ExpressionContext.Expression)
- {
- throw new NotSupportedException("Lifted expressions remaining at top-level in expression context");
- }
+ throw new NotSupportedException("Lifted expressions remaining at top-level in expression context");
}
Check.DebugAssert(_stack.Count == 1, "_parameterStack.Count == 1");
@@ -351,77 +354,57 @@ protected override Expression VisitBinary(BinaryExpression binary)
Expression VisitAssignment(BinaryExpression assignment, SyntaxKind kind)
{
- var translatedLeft = Translate(assignment.Left);
-
- ExpressionSyntax translatedRight;
-
- // LINQ expression trees can directly access private members, but C# code cannot.
- // If a private member is being set, VisitMember generated a reflection GetValue invocation for it; detect
- // that here and replace it with SetValue instead.
- // TODO: Replace this with a more efficient API for .NET 8.0.
- // TODO: Private property
- if (translatedLeft is InvocationExpressionSyntax
- {
- Expression: MemberAccessExpressionSyntax
- {
- Name.Identifier.Text: nameof(FieldInfo.GetValue),
- Expression: var fieldInfoExpression
- },
- ArgumentList.Arguments: [var lValue]
- })
+ if (assignment.Left is MemberExpression { Member: FieldInfo { IsPublic: false } } member)
{
- // If we have a simple assignment, use the RHS directly (fieldInfo.SetValue(lValue, rValue)).
- // For compound assignment operators, apply the appropriate operator (fieldInfo.setValue(lValue, rValue + lValue)
- translatedRight = Translate(assignment.Right);
-
+ // For compound assignment operators, apply the appropriate operator before translating
if (kind != SyntaxKind.SimpleAssignmentExpression)
{
- var nonAssignmentOperator = kind switch
+ var expandedRight = kind switch
{
- SyntaxKind.AddAssignmentExpression => SyntaxKind.AddExpression,
- SyntaxKind.MultiplyAssignmentExpression => SyntaxKind.MultiplyExpression,
- SyntaxKind.DivideAssignmentExpression => SyntaxKind.DivideExpression,
- SyntaxKind.ModuloAssignmentExpression => SyntaxKind.ModuloExpression,
- SyntaxKind.SubtractAssignmentExpression => SyntaxKind.SubtractExpression,
- SyntaxKind.AndAssignmentExpression => SyntaxKind.BitwiseAndExpression,
- SyntaxKind.OrAssignmentExpression => SyntaxKind.BitwiseOrExpression,
- SyntaxKind.LeftShiftAssignmentExpression => SyntaxKind.LeftShiftExpression,
- SyntaxKind.RightShiftAssignmentExpression => SyntaxKind.RightShiftExpression,
- SyntaxKind.ExclusiveOrAssignmentExpression => SyntaxKind.ExclusiveOrExpression,
+ SyntaxKind.AddAssignmentExpression => E.Add(assignment.Left, assignment.Right),
+ SyntaxKind.MultiplyAssignmentExpression => E.Multiply(assignment.Left, assignment.Right),
+ SyntaxKind.DivideAssignmentExpression => E.Divide(assignment.Left, assignment.Right),
+ SyntaxKind.ModuloAssignmentExpression => E.Modulo(assignment.Left, assignment.Right),
+ SyntaxKind.SubtractAssignmentExpression => E.Subtract(assignment.Left, assignment.Right),
+ SyntaxKind.AndAssignmentExpression => E.And(assignment.Left, assignment.Right),
+ SyntaxKind.OrAssignmentExpression => E.Or(assignment.Left, assignment.Right),
+ SyntaxKind.ExclusiveOrAssignmentExpression => E.ExclusiveOr(assignment.Left, assignment.Right),
+ SyntaxKind.LeftShiftAssignmentExpression => E.LeftShift(assignment.Left, assignment.Right),
+ SyntaxKind.RightShiftAssignmentExpression => E.RightShift(assignment.Left, assignment.Right),
_ => throw new UnreachableException()
};
- translatedRight = BinaryExpression(nonAssignmentOperator, translatedLeft, translatedRight);
+ Result = Translate(E.Assign(assignment.Left, expandedRight));
+
+ return assignment;
}
- Result = InvocationExpression(
- MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- fieldInfoExpression,
- IdentifierName(nameof(FieldInfo.SetValue))),
- ArgumentList(
- SeparatedList(new[] { lValue, Argument(translatedRight) })));
+ TranslateNonPublicFieldAssignment(member, assignment.Right);
+
+ return assignment;
+ }
+
+ // TODO: Private property
+
+ var translatedLeft = Translate(assignment.Left);
+
+ // Identify assignment where the RHS supports assignment lowering (switch, conditional). If the e.g. switch expression is
+ // lifted out (because some arm contains a block), this will lower the variable to be assigned inside the resulting switch
+ // statement, rather then adding another useless temporary variable.
+ var translatedRight = Translate(
+ assignment.Right,
+ lowerableAssignmentVariable: translatedLeft as IdentifierNameSyntax);
+
+ // If the RHS was lifted out and the assignment lowering succeeded, Translate above returns the lowered assignment variable;
+ // this would mean that we return a useless identity assignment (i = i). Instead, just return it.
+ if (translatedRight == translatedLeft)
+ {
+ Result = translatedRight;
}
else
{
- // Identify assignment where the RHS supports assignment lowering (switch, conditional). If the e.g. switch expression is
- // lifted out (because some arm contains a block), this will lower the variable to be assigned inside the resulting switch
- // statement, rather then adding another useless temporary variable.
- translatedRight = Translate(
- assignment.Right,
- lowerableAssignmentVariable: translatedLeft as IdentifierNameSyntax);
-
- // If the RHS was lifted out and the assignment lowering succeeded, Translate above returns the lowered assignment variable;
- // this would mean that we return a useless identity assignment (i = i). Instead, just return it.
- if (translatedRight == translatedLeft)
- {
- Result = translatedRight;
- }
- else
- {
- Result = AssignmentExpression(kind, translatedLeft, translatedRight);
- }
+ Result = AssignmentExpression(kind, translatedLeft, translatedRight);
}
return assignment;
@@ -461,6 +444,11 @@ protected override Expression VisitBlock(BlockExpression block)
if (blockContext == ExpressionContext.Expression)
{
+ if (_liftedState.Variables.ContainsKey(parameter))
+ {
+ throw new NotSupportedException("Parameter clash during expression lifting for: " + parameter.Name);
+ }
+
_liftedState.Variables.Add(parameter, uniquifiedName);
_liftedState.VariableNames.Add(uniquifiedName);
}
@@ -525,7 +513,7 @@ protected override Expression VisitBlock(BlockExpression block)
var useExplicitVariableType = valueSyntax.Kind() == SyntaxKind.NullLiteralExpression;
translated = useExplicitVariableType
- ? _g.LocalDeclarationStatement(Translate(lValue.Type), LookupVariableName(lValue), valueSyntax)
+ ? _g.LocalDeclarationStatement(Generate(lValue.Type), LookupVariableName(lValue), valueSyntax)
: _g.LocalDeclarationStatement(LookupVariableName(lValue), valueSyntax);
}
@@ -625,7 +613,7 @@ protected override Expression VisitBlock(BlockExpression block)
// and either add them to the block, or lift them if we're an expression block.
var unassignedVariableDeclarations =
unassignedVariables.Select(
- v => (LocalDeclarationStatementSyntax)_g.LocalDeclarationStatement(Translate(v.Type), LookupVariableName(v)));
+ v => (LocalDeclarationStatementSyntax)_g.LocalDeclarationStatement(Generate(v.Type), LookupVariableName(v)));
if (blockContext == ExpressionContext.Expression)
{
@@ -743,7 +731,7 @@ protected virtual SyntaxNode TranslateCatchBlock(CatchBlock catchBlock, bool noT
var catchDeclaration = noType
? null
- : CatchDeclaration(Translate(catchBlock.Test));
+ : CatchDeclaration(Generate(catchBlock.Test));
if (catchBlock.Variable is not null)
{
@@ -797,7 +785,7 @@ protected virtual CSharpSyntaxNode TranslateConditional(
if (isFalseAbsent)
{
throw new NotSupportedException(
- $"Missing {nameof(ConditionalExpression.IfFalse)} in {nameof(ConditionalExpression)} in expression context");
+ $"Missing {nameof(System.Linq.Expressions.ConditionalExpression.IfFalse)} in {nameof(System.Linq.Expressions.ConditionalExpression)} in expression context");
}
var parentLiftedState = _liftedState;
@@ -819,7 +807,7 @@ protected virtual CSharpSyntaxNode TranslateConditional(
if (_liftedState.Statements.Count == 0)
{
_liftedState = parentLiftedState;
- return ConditionalExpression(test, ifTrueExpression, ifFalseExpression);
+ return ParenthesizedExpression(ConditionalExpression(test, ifTrueExpression, ifFalseExpression));
}
}
@@ -849,7 +837,7 @@ protected virtual CSharpSyntaxNode TranslateConditional(
var name = UniquifyVariableName("liftedConditional");
var parameter = E.Parameter(conditional.Type, name);
assignmentVariable = IdentifierName(name);
- loweredAssignmentVariableType = Translate(parameter.Type);
+ loweredAssignmentVariableType = Generate(parameter.Type);
}
else
{
@@ -943,112 +931,146 @@ protected override Expression VisitConstant(ConstantExpression constant)
Result = GenerateValue(constant.Value);
return constant;
+ }
- ExpressionSyntax GenerateValue(object? value)
- => value switch
- {
- int or long or uint or ulong or short or sbyte or ushort or byte or double or float or decimal or char
- => (ExpressionSyntax)_g.LiteralExpression(constant.Value),
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected virtual ExpressionSyntax GenerateValue(object? value)
+ {
+ if (_constantReplacements != null
+ && value != null
+ && _constantReplacements.TryGetValue(value, out var instance))
+ {
+ return instance;
+ }
- string or bool or null => (ExpressionSyntax)_g.LiteralExpression(constant.Value),
+ return value switch
+ {
+ int or long or uint or ulong or short or sbyte or ushort or byte or double or float or decimal or char
+ or string or bool or null
+ => (ExpressionSyntax)_g.LiteralExpression(value),
- Type t => TypeOfExpression(Translate(t)),
- Enum e => HandleEnum(e),
+ Type t => TypeOfExpression(Generate(t)),
+ Enum e => HandleEnum(e),
- ITuple tuple
- when tuple.GetType() is { IsGenericType: true } tupleType
- && tupleType.Name.StartsWith("ValueTuple`", StringComparison.Ordinal)
- && tupleType.Namespace == "System"
- => HandleValueTuple(tuple),
+ Guid g => ObjectCreationExpression(IdentifierName(nameof(Guid)))
+ .WithArgumentList(
+ ArgumentList(
+ SingletonSeparatedList(
+ Argument(
+ LiteralExpression(
+ SyntaxKind.StringLiteralExpression,
+ Literal(g.ToString())))))),
- IEqualityComparer c
- when c == StructuralComparisons.StructuralEqualityComparer
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(StructuralComparisons)),
- IdentifierName(nameof(StructuralComparisons.StructuralEqualityComparer))),
+ ITuple tuple
+ when tuple.GetType() is { IsGenericType: true } tupleType
+ && tupleType.Name.StartsWith("ValueTuple`", StringComparison.Ordinal)
+ && tupleType.Namespace == "System"
+ => HandleValueTuple(tuple),
- CultureInfo cultureInfo when cultureInfo == CultureInfo.InvariantCulture
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(CultureInfo)),
- IdentifierName(nameof(CultureInfo.InvariantCulture))),
+ ReferenceEqualityComparer equalityComparer
+ when equalityComparer == ReferenceEqualityComparer.Instance
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(ReferenceEqualityComparer)),
+ IdentifierName(nameof(ReferenceEqualityComparer.Instance))),
- CultureInfo cultureInfo when cultureInfo == CultureInfo.InstalledUICulture
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(CultureInfo)),
- IdentifierName(nameof(CultureInfo.InstalledUICulture))),
+ IEqualityComparer c
+ when c == StructuralComparisons.StructuralEqualityComparer
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(StructuralComparisons)),
+ IdentifierName(nameof(StructuralComparisons.StructuralEqualityComparer))),
- CultureInfo cultureInfo when cultureInfo == CultureInfo.CurrentCulture
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(CultureInfo)),
- IdentifierName(nameof(CultureInfo.CurrentCulture))),
+ CultureInfo cultureInfo when cultureInfo == CultureInfo.InvariantCulture
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(CultureInfo)),
+ IdentifierName(nameof(CultureInfo.InvariantCulture))),
- CultureInfo cultureInfo when cultureInfo == CultureInfo.CurrentUICulture
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(CultureInfo)),
- IdentifierName(nameof(CultureInfo.CurrentUICulture))),
+ CultureInfo cultureInfo when cultureInfo == CultureInfo.InstalledUICulture
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(CultureInfo)),
+ IdentifierName(nameof(CultureInfo.InstalledUICulture))),
- CultureInfo cultureInfo when cultureInfo == CultureInfo.DefaultThreadCurrentCulture
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(CultureInfo)),
- IdentifierName(nameof(CultureInfo.DefaultThreadCurrentCulture))),
+ CultureInfo cultureInfo when cultureInfo == CultureInfo.CurrentCulture
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(CultureInfo)),
+ IdentifierName(nameof(CultureInfo.CurrentCulture))),
- CultureInfo cultureInfo when cultureInfo == CultureInfo.DefaultThreadCurrentUICulture
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(CultureInfo)),
- IdentifierName(nameof(CultureInfo.DefaultThreadCurrentUICulture))),
+ CultureInfo cultureInfo when cultureInfo == CultureInfo.CurrentUICulture
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(CultureInfo)),
+ IdentifierName(nameof(CultureInfo.CurrentUICulture))),
- Encoding encoding when encoding == Encoding.ASCII
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(Encoding)),
- IdentifierName(nameof(Encoding.ASCII))),
+ CultureInfo cultureInfo when cultureInfo == CultureInfo.DefaultThreadCurrentCulture
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(CultureInfo)),
+ IdentifierName(nameof(CultureInfo.DefaultThreadCurrentCulture))),
- Encoding encoding when encoding == Encoding.Unicode
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(Encoding)),
- IdentifierName(nameof(Encoding.Unicode))),
+ CultureInfo cultureInfo when cultureInfo == CultureInfo.DefaultThreadCurrentUICulture
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(CultureInfo)),
+ IdentifierName(nameof(CultureInfo.DefaultThreadCurrentUICulture))),
- Encoding encoding when encoding == Encoding.BigEndianUnicode
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(Encoding)),
- IdentifierName(nameof(Encoding.BigEndianUnicode))),
+ Encoding encoding when encoding == Encoding.ASCII
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(Encoding)),
+ IdentifierName(nameof(Encoding.ASCII))),
- Encoding encoding when encoding == Encoding.UTF8
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(Encoding)),
- IdentifierName(nameof(Encoding.UTF8))),
+ Encoding encoding when encoding == Encoding.Unicode
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(Encoding)),
+ IdentifierName(nameof(Encoding.Unicode))),
- Encoding encoding when encoding == Encoding.UTF32
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(Encoding)),
- IdentifierName(nameof(Encoding.UTF32))),
+ Encoding encoding when encoding == Encoding.BigEndianUnicode
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(Encoding)),
+ IdentifierName(nameof(Encoding.BigEndianUnicode))),
- Encoding encoding when encoding == Encoding.Latin1
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(Encoding)),
- IdentifierName(nameof(Encoding.Latin1))),
+ Encoding encoding when encoding == Encoding.UTF8
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(Encoding)),
+ IdentifierName(nameof(Encoding.UTF8))),
- Encoding encoding when encoding == Encoding.Default
- => MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- Translate(typeof(Encoding)),
- IdentifierName(nameof(Encoding.Default))),
+ Encoding encoding when encoding == Encoding.UTF32
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(Encoding)),
+ IdentifierName(nameof(Encoding.UTF32))),
- _ => throw new NotSupportedException(
- $"Encountered a constant of unsupported type '{value.GetType().Name}'. Only primitive constant nodes are supported.")
- };
+ Encoding encoding when encoding == Encoding.Latin1
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(Encoding)),
+ IdentifierName(nameof(Encoding.Latin1))),
+
+ Encoding encoding when encoding == Encoding.Default
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(Encoding)),
+ IdentifierName(nameof(Encoding.Default))),
+
+ FieldInfo fieldInfo
+ => HandleFieldInfo(fieldInfo),
+
+ //TODO: Handle PropertyInfo
+
+ _ => GenerateUnknownValue(value)
+ };
ExpressionSyntax HandleEnum(Enum e)
{
@@ -1063,7 +1085,7 @@ ExpressionSyntax HandleEnum(Enum e)
var underlyingType = enumType.GetEnumUnderlyingType();
return CastExpression(
- Translate(enumType),
+ Generate(enumType),
LiteralExpression(
SyntaxKind.NumericLiteralExpression,
underlyingType == typeof(sbyte)
@@ -1083,13 +1105,13 @@ ExpressionSyntax HandleEnum(Enum e)
(last, next) => last is null
? MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
- IdentifierName(enumType.Name),
+ Generate(enumType),
IdentifierName(next))
: BinaryExpression(
SyntaxKind.BitwiseOrExpression, last,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
- IdentifierName(enumType.Name),
+ Generate(enumType),
IdentifierName(next))))!;
}
@@ -1103,6 +1125,47 @@ ExpressionSyntax HandleValueTuple(ITuple tuple)
return TupleExpression(SeparatedList(arguments));
}
+
+ ExpressionSyntax HandleFieldInfo(FieldInfo fieldInfo)
+ => fieldInfo.DeclaringType is null
+ ? throw new NotSupportedException("Field without a declaring type: " + fieldInfo.Name)
+ : (ExpressionSyntax)InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ TypeOfExpression(Generate(fieldInfo.DeclaringType)),
+ IdentifierName(nameof(Type.GetField))),
+ ArgumentList(
+ SeparatedList(new[] {
+ Argument(LiteralExpression(
+ SyntaxKind.StringLiteralExpression,
+ Literal(fieldInfo.Name))),
+ Argument(BinaryExpression(
+ SyntaxKind.BitwiseOrExpression,
+ HandleEnum(fieldInfo.IsStatic ? BindingFlags.Static : BindingFlags.Instance),
+ BinaryExpression(
+ SyntaxKind.BitwiseOrExpression,
+ HandleEnum(fieldInfo.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic),
+ HandleEnum(BindingFlags.DeclaredOnly)))) })));
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected virtual ExpressionSyntax GenerateUnknownValue(object value)
+ {
+ var type = value.GetType();
+ if (type.IsValueType
+ && value.Equals(type.GetDefaultValue()))
+ {
+ return DefaultExpression(Generate(type));
+ }
+
+ throw new NotSupportedException(
+ $"Encountered a constant of unsupported type '{value.GetType().Name}'. Only primitive constant nodes are supported."
+ + Environment.NewLine + value);
}
///
@@ -1112,7 +1175,7 @@ protected override Expression VisitDebugInfo(DebugInfoExpression node)
///
protected override Expression VisitDefault(DefaultExpression node)
{
- Result = DefaultExpression(Translate(node.Type));
+ Result = DefaultExpression(Generate(node.Type));
return node;
}
@@ -1203,13 +1266,45 @@ protected virtual IdentifierNameSyntax TranslateLabelTarget(LabelTarget labelTar
return IdentifierName(_stack.Peek().Labels[labelTarget]);
}
- private TypeSyntax Translate(Type type)
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected virtual TypeSyntax Generate(Type type)
{
if (type.IsGenericType)
{
- return GenericName(
+ // This should produce terser code, but currently gets broken by the Simplifier
+ //if (type.IsConstructedGenericType
+ // && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+ //{
+ // return NullableType(Translate(type.GenericTypeArguments[0]));
+ //}
+
+ var generic = GenericName(
Identifier(type.Name.Substring(0, type.Name.IndexOf('`'))),
- TypeArgumentList(SeparatedList(type.GenericTypeArguments.Select(Translate))));
+ TypeArgumentList(SeparatedList(type.GenericTypeArguments.Select(Generate))));
+ if (type.IsNested)
+ {
+ return QualifiedName(
+ (NameSyntax)Generate(type.DeclaringType!),
+ generic);
+ }
+
+ if (type.Namespace != null)
+ {
+ _collectedNamespaces.Add(type.Namespace);
+ }
+
+ return generic;
+ }
+
+ if (type.IsArray)
+ {
+ return ArrayType(Generate(type.GetElementType()!))
+ .WithRankSpecifiers(SingletonList(ArrayRankSpecifier(SingletonSeparatedList(OmittedArraySizeExpression()))));
}
if (type == typeof(string))
@@ -1295,7 +1390,7 @@ private TypeSyntax Translate(Type type)
if (type.IsNested)
{
return QualifiedName(
- (NameSyntax)Translate(type.DeclaringType!),
+ (NameSyntax)Generate(type.DeclaringType!),
IdentifierName(type.Name));
}
@@ -1346,7 +1441,7 @@ protected override Expression VisitLambda(Expression lambda)
lambda.Parameters.Select(
p =>
Parameter(Identifier(LookupVariableName(p)))
- .WithType(p.Type.IsAnonymousType() ? null : Translate(p.Type))))),
+ .WithType(p.Type.IsAnonymousType() ? null : Generate(p.Type))))),
body);
var popped = _stack.Pop();
@@ -1417,31 +1512,10 @@ protected override Expression VisitMember(MemberExpression member)
{
using var _ = ChangeContext(ExpressionContext.Expression);
- // LINQ expression trees can directly access private members, but C# code cannot; render (slow) reflection code that does the same
- // thing. Note that assignment to private members is handled in VisitBinary.
- // TODO: Replace this with a more efficient UnsafeAccessor API. #29754
switch (member)
{
- case { Member: FieldInfo { IsPrivate: true } fieldInfo }:
- if (member.Expression is null)
- {
- throw new NotImplementedException("Private static field access");
- }
-
- if (member.Member.DeclaringType is null)
- {
- throw new NotSupportedException("Private field without a declaring type: " + member.Member.Name);
- }
-
- Result = Translate(
- E.Call(
- E.Call(
- E.Constant(member.Member.DeclaringType),
- _typeGetFieldMethod ??= typeof(Type).GetMethod(nameof(Type.GetField), [typeof(string), typeof(BindingFlags)])!,
- E.Constant(fieldInfo.Name),
- E.Constant(BindingFlags.NonPublic | BindingFlags.Instance)),
- _fieldGetValueMethod ??= typeof(FieldInfo).GetMethod(nameof(FieldInfo.GetValue), [typeof(object)])!,
- member.Expression));
+ case { Member: FieldInfo { IsPublic: false } }:
+ TranslateNonPublicFieldAccess(member);
break;
// TODO: private property
@@ -1458,7 +1532,7 @@ when constantExpression.Type.Attributes.HasFlag(TypeAttributes.NestedPrivate)
Result = MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
member.Expression is null
- ? Translate(member.Member.DeclaringType!) // static
+ ? Generate(member.Member.DeclaringType!) // static
: Translate(member.Expression),
IdentifierName(member.Member.Name));
break;
@@ -1467,6 +1541,55 @@ member.Expression is null
return member;
}
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected virtual void TranslateNonPublicFieldAccess(MemberExpression member)
+ {
+ if (member.Expression is null)
+ {
+ throw new NotImplementedException("Private static field access");
+ }
+
+ var translatedExpression = Translate(member.Expression);
+ Result = ParenthesizedExpression(
+ CastExpression(
+ Generate(member.Type),
+ InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ GenerateValue(member.Member),
+ IdentifierName(nameof(FieldInfo.GetValue))),
+ ArgumentList(
+ SingletonSeparatedList(Argument(translatedExpression))))));
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected virtual void TranslateNonPublicFieldAssignment(MemberExpression member, Expression value)
+ {
+ // LINQ expression trees can directly access private members, but C# code cannot, use SetValue instead.
+ if (member.Expression is null)
+ {
+ throw new NotImplementedException("Private static field assignment");
+ }
+
+ Result = InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ GenerateValue(member.Member),
+ IdentifierName(nameof(FieldInfo.SetValue))),
+ ArgumentList(
+ SeparatedList(new[] { Argument(Translate(member.Expression)), Argument(Translate(value)) })));
+ }
+
///
protected override Expression VisitIndex(IndexExpression index)
{
@@ -1518,7 +1641,7 @@ protected override Expression VisitMethodCall(MethodCallExpression call)
Identifier(call.Method.Name),
TypeArgumentList(
SeparatedList(
- call.Method.GetGenericArguments().Select(Translate))));
+ call.Method.GetGenericArguments().Select(Generate))));
}
// Extension syntax
@@ -1549,7 +1672,7 @@ protected override Expression VisitMethodCall(MethodCallExpression call)
ExpressionSyntax GetMemberAccessesForAllDeclaringTypes(Type type)
=> type.DeclaringType is null
- ? Translate(type)
+ ? Generate(type)
: MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
GetMemberAccessesForAllDeclaringTypes(type.DeclaringType),
@@ -1620,7 +1743,7 @@ protected override Expression VisitNewArray(NewArrayExpression newArray)
{
using var _ = ChangeContext(ExpressionContext.Expression);
- var elementType = Translate(newArray.Type.GetElementType()!);
+ var elementType = Generate(newArray.Type.GetElementType()!);
var expressions = TranslateList(newArray.Expressions);
if (newArray.NodeType == ExpressionType.NewArrayBounds)
@@ -1698,7 +1821,7 @@ protected override Expression VisitNew(NewExpression node)
{
// Normal case with plain old instantiation
Result = ObjectCreationExpression(
- Translate(node.Type),
+ Generate(node.Type),
ArgumentList(SeparatedList(arguments)),
initializer: null);
}
@@ -1863,7 +1986,7 @@ SyntaxList ProcessArmBody(Expression body)
var name = UniquifyVariableName("liftedSwitch");
var parameter = E.Parameter(switchNode.Type, name);
assignmentVariable = IdentifierName(name);
- loweredAssignmentVariableType = Translate(parameter.Type);
+ loweredAssignmentVariableType = Generate(parameter.Type);
}
else
{
@@ -2024,10 +2147,10 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression node)
Result = node.NodeType switch
{
ExpressionType.TypeIs
- => BinaryExpression(SyntaxKind.IsExpression, visitedExpression, Translate(node.TypeOperand)),
+ => BinaryExpression(SyntaxKind.IsExpression, visitedExpression, Generate(node.TypeOperand)),
ExpressionType.TypeEqual
- => BinaryExpression(SyntaxKind.EqualsExpression, visitedExpression, TypeOfExpression(Translate(node.TypeOperand))),
+ => BinaryExpression(SyntaxKind.EqualsExpression, visitedExpression, TypeOfExpression(Generate(node.TypeOperand))),
_ => throw new ArgumentOutOfRangeException()
};
@@ -2063,12 +2186,12 @@ protected override Expression VisitUnary(UnaryExpression unary)
ExpressionType.IsFalse => _g.LogicalNotExpression(operand),
ExpressionType.IsTrue => operand,
ExpressionType.ArrayLength => _g.MemberAccessExpression(operand, "Length"),
- ExpressionType.Convert => ParenthesizedExpression((ExpressionSyntax)_g.ConvertExpression(Translate(unary.Type), operand)),
+ ExpressionType.Convert => ParenthesizedExpression((ExpressionSyntax)_g.ConvertExpression(Generate(unary.Type), operand)),
ExpressionType.ConvertChecked =>
- ParenthesizedExpression((ExpressionSyntax)_g.ConvertExpression(Translate(unary.Type), operand)),
+ ParenthesizedExpression((ExpressionSyntax)_g.ConvertExpression(Generate(unary.Type), operand)),
ExpressionType.Throw when unary.Type == typeof(void) => _g.ThrowStatement(operand),
ExpressionType.Throw => _g.ThrowExpression(operand),
- ExpressionType.TypeAs => BinaryExpression(SyntaxKind.AsExpression, operand, Translate(unary.Type)),
+ ExpressionType.TypeAs => BinaryExpression(SyntaxKind.AsExpression, operand, Generate(unary.Type)),
ExpressionType.Quote => operand,
ExpressionType.UnaryPlus => PrefixUnaryExpression(SyntaxKind.UnaryPlusExpression, operand),
ExpressionType.Unbox => operand,
diff --git a/src/EFCore.Design/Query/Internal/RuntimeModelLinqToCSharpSyntaxTranslator.cs b/src/EFCore.Design/Query/Internal/RuntimeModelLinqToCSharpSyntaxTranslator.cs
new file mode 100644
index 00000000000..f0f30f54861
--- /dev/null
+++ b/src/EFCore.Design/Query/Internal/RuntimeModelLinqToCSharpSyntaxTranslator.cs
@@ -0,0 +1,134 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// ReSharper disable once CheckNamespace
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
+using Microsoft.EntityFrameworkCore.Design.Internal;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Microsoft.EntityFrameworkCore.Query.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public class RuntimeModelLinqToCSharpSyntaxTranslator : LinqToCSharpSyntaxTranslator
+{
+ private Dictionary? _memberAccessReplacements;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public RuntimeModelLinqToCSharpSyntaxTranslator(SyntaxGenerator syntaxGenerator) : base(syntaxGenerator)
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual SyntaxNode TranslateStatement(
+ Expression node,
+ Dictionary? constantReplacements,
+ Dictionary? memberAccessReplacements,
+ ISet collectedNamespaces)
+ {
+ _memberAccessReplacements = memberAccessReplacements;
+ var result = TranslateStatement(node, constantReplacements, collectedNamespaces);
+ _memberAccessReplacements = null;
+ return result;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual SyntaxNode TranslateExpression(
+ Expression node,
+ Dictionary? constantReplacements,
+ Dictionary? memberAccessReplacements,
+ ISet collectedNamespaces)
+ {
+ _memberAccessReplacements = memberAccessReplacements;
+ var result = TranslateExpression(node, constantReplacements, collectedNamespaces);
+ _memberAccessReplacements = null;
+ return result;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override ExpressionSyntax GenerateValue(object? value)
+ => value switch
+ {
+ Snapshot snapshot
+ when snapshot == Snapshot.Empty
+ => MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ Generate(typeof(Snapshot)),
+ IdentifierName(nameof(Snapshot.Empty))),
+
+ _ => base.GenerateValue(value)
+ };
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override void TranslateNonPublicFieldAccess(MemberExpression member)
+ {
+ if (_memberAccessReplacements?.TryGetValue(new MemberAccess(member.Member, assignment: false), out var methodName) == true)
+ {
+ Result = InvocationExpression(
+ methodName,
+ ArgumentList(SeparatedList(new[] { Argument(Translate(member.Expression)) })));
+ }
+ else
+ {
+ base.TranslateNonPublicFieldAccess(member);
+ }
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override void TranslateNonPublicFieldAssignment(MemberExpression member, Expression value)
+ {
+ if (_memberAccessReplacements?.TryGetValue(new MemberAccess(member.Member, assignment: true), out var methodName) == true)
+ {
+ Result = InvocationExpression(
+ methodName,
+ ArgumentList(SeparatedList(new[]
+ {
+ Argument(Translate(member.Expression)),
+ Argument(Translate(value))
+ })));
+ }
+ else
+ {
+ base.TranslateNonPublicFieldAssignment(member, value);
+ }
+ }
+}
diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
index ce13fabe048..007b2d072b7 100644
--- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
@@ -1,7 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Runtime.CompilerServices;
using System.Text;
+using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -64,18 +66,18 @@ public virtual IReadOnlyCollection GenerateModel(
var modelFileName = options.ContextType.ShortDisplayName() + ModelSuffix + FileExtension;
scaffoldedFiles.Add(new ScaffoldedFile { Path = modelFileName, Code = modelCode });
- var entityTypeIds = new Dictionary();
+ var configurationClassNames = new Dictionary();
var modelBuilderCode = CreateModelBuilder(
- model, options.ModelNamespace, options.ContextType, entityTypeIds, nullable);
+ model, options.ModelNamespace, options.ContextType, configurationClassNames, nullable);
var modelBuilderFileName = options.ContextType.ShortDisplayName() + ModelBuilderSuffix + FileExtension;
scaffoldedFiles.Add(new ScaffoldedFile { Path = modelBuilderFileName, Code = modelBuilderCode });
- foreach (var (entityType, (_, @class)) in entityTypeIds)
+ foreach (var entityType in model.GetEntityTypesInHierarchicalOrder())
{
var generatedCode = GenerateEntityType(
- entityType, options.ModelNamespace, @class, nullable);
+ entityType, options.ModelNamespace, configurationClassNames, nullable);
- var entityTypeFileName = @class + FileExtension;
+ var entityTypeFileName = configurationClassNames[entityType] + FileExtension;
scaffoldedFiles.Add(new ScaffoldedFile { Path = entityTypeFileName, Code = generatedCode });
}
@@ -202,7 +204,7 @@ private string CreateModelBuilder(
IModel model,
string @namespace,
Type contextType,
- Dictionary entityTypeIds,
+ Dictionary configurationClassNames,
bool nullable)
{
var mainBuilder = new IndentedStringBuilder();
@@ -260,6 +262,7 @@ private string CreateModelBuilder(
{
var entityTypes = model.GetEntityTypesInHierarchicalOrder();
var variables = new HashSet();
+ var scopeVariables = new Dictionary();
var anyEntityTypes = false;
foreach (var entityType in entityTypes)
@@ -272,7 +275,8 @@ private string CreateModelBuilder(
? EntityTypeSuffix + variableName[1..]
: char.ToUpperInvariant(firstChar) + variableName[(variableName[0] == '@' ? 2 : 1)..] + EntityTypeSuffix;
- entityTypeIds[entityType] = (variableName, entityClassName);
+ configurationClassNames[entityType] = entityClassName;
+ scopeVariables[entityType] = variableName;
mainBuilder
.Append("var ")
@@ -285,7 +289,7 @@ private string CreateModelBuilder(
{
mainBuilder
.Append(", ")
- .Append(entityTypeIds[entityType.BaseType].Variable);
+ .Append(scopeVariables[entityType.BaseType]);
}
mainBuilder
@@ -298,21 +302,20 @@ private string CreateModelBuilder(
}
var anyForeignKeys = false;
- foreach (var (entityType, namePair) in entityTypeIds)
+ foreach (var entityType in entityTypes)
{
var foreignKeyNumber = 1;
- var (variableName, entityClassName) = namePair;
foreach (var foreignKey in entityType.GetDeclaredForeignKeys())
{
anyForeignKeys = true;
- var principalVariable = entityTypeIds[foreignKey.PrincipalEntityType].Variable;
+ var principalVariable = scopeVariables[foreignKey.PrincipalEntityType];
mainBuilder
- .Append(entityClassName)
+ .Append(configurationClassNames[entityType])
.Append(".CreateForeignKey")
.Append(foreignKeyNumber++.ToString())
.Append("(")
- .Append(variableName)
+ .Append(scopeVariables[entityType])
.Append(", ")
.Append(principalVariable)
.AppendLine(");");
@@ -325,22 +328,21 @@ private string CreateModelBuilder(
}
var anySkipNavigations = false;
- foreach (var (entityType, namePair) in entityTypeIds)
+ foreach (var entityType in entityTypes)
{
var navigationNumber = 1;
- var (variableName, entityClassName) = namePair;
foreach (var navigation in entityType.GetDeclaredSkipNavigations())
{
anySkipNavigations = true;
- var targetVariable = entityTypeIds[navigation.TargetEntityType].Variable;
- var joinVariable = entityTypeIds[navigation.JoinEntityType].Variable;
+ var targetVariable = scopeVariables[navigation.TargetEntityType];
+ var joinVariable = scopeVariables[navigation.JoinEntityType];
mainBuilder
- .Append(entityClassName)
+ .Append(configurationClassNames[entityType])
.Append(".CreateSkipNavigation")
.Append(navigationNumber++.ToString())
.Append("(")
- .Append(variableName)
+ .Append(scopeVariables[entityType])
.Append(", ")
.Append(targetVariable)
.Append(", ")
@@ -354,15 +356,13 @@ private string CreateModelBuilder(
mainBuilder.AppendLine();
}
- foreach (var (_, namePair) in entityTypeIds)
+ foreach (var (entityType, entityClassName) in configurationClassNames)
{
- var (variableName, entityClassName) = namePair;
-
mainBuilder
.Append(entityClassName)
.Append(".CreateAnnotations")
.Append("(")
- .Append(variableName)
+ .Append(scopeVariables[entityType])
.AppendLine(");");
}
@@ -378,6 +378,7 @@ private string CreateModelBuilder(
methodBuilder,
namespaces,
variables,
+ configurationClassNames,
nullable);
foreach (var typeConfiguration in model.GetTypeMappingConfigurations())
@@ -484,7 +485,7 @@ private void Create(
mainBuilder.AppendLine();
}
- private string GenerateEntityType(IEntityType entityType, string @namespace, string className, bool nullable)
+ private string GenerateEntityType(IEntityType entityType, string @namespace, Dictionary entityClassNames, bool nullable)
{
var mainBuilder = new IndentedStringBuilder();
var methodBuilder = new IndentedStringBuilder();
@@ -501,31 +502,38 @@ private string GenerateEntityType(IEntityType entityType, string @namespace, str
mainBuilder.Indent();
}
+ var className = entityClassNames[entityType];
mainBuilder
.Append("internal partial class ").AppendLine(className)
.AppendLine("{");
using (mainBuilder.Indent())
{
- CreateEntityType(entityType, mainBuilder, methodBuilder, namespaces, className, nullable);
+ CreateEntityType(entityType, mainBuilder, methodBuilder, namespaces, entityClassNames, nullable);
foreach (var complexProperty in entityType.GetDeclaredComplexProperties())
{
- CreateComplexProperty(complexProperty, mainBuilder, methodBuilder, namespaces, className, nullable);
+ CreateComplexProperty(complexProperty, mainBuilder, methodBuilder, namespaces, entityClassNames, className, nullable);
}
var foreignKeyNumber = 1;
foreach (var foreignKey in entityType.GetDeclaredForeignKeys())
{
- CreateForeignKey(foreignKey, foreignKeyNumber++, mainBuilder, methodBuilder, namespaces, className, nullable);
+ CreateForeignKey(foreignKey, foreignKeyNumber++, mainBuilder, methodBuilder, namespaces, entityClassNames, className, nullable);
}
var navigationNumber = 1;
foreach (var navigation in entityType.GetDeclaredSkipNavigations())
{
- CreateSkipNavigation(navigation, navigationNumber++, mainBuilder, methodBuilder, namespaces, className, nullable);
+ CreateSkipNavigation(navigation, navigationNumber++, mainBuilder, methodBuilder, namespaces, entityClassNames, className, nullable);
}
- CreateAnnotations(entityType, mainBuilder, methodBuilder, namespaces, className, nullable);
+ CreateAnnotations(entityType, mainBuilder, methodBuilder, namespaces, entityClassNames, nullable);
+
+ var methods = methodBuilder.ToString();
+ if (!string.IsNullOrEmpty(methods))
+ {
+ mainBuilder.AppendLines(methods);
+ }
}
mainBuilder.AppendLine("}");
@@ -536,7 +544,7 @@ private string GenerateEntityType(IEntityType entityType, string @namespace, str
mainBuilder.AppendLine("}");
}
- return GenerateHeader(namespaces, @namespace, nullable) + mainBuilder + methodBuilder;
+ return GenerateHeader(namespaces, @namespace, nullable) + mainBuilder;
}
private void CreateEntityType(
@@ -544,7 +552,7 @@ private void CreateEntityType(
IndentedStringBuilder mainBuilder,
IndentedStringBuilder methodBuilder,
SortedSet namespaces,
- string className,
+ Dictionary configurationClassNames,
bool nullable)
{
mainBuilder
@@ -560,6 +568,7 @@ private void CreateEntityType(
mainBuilder.AppendLine(" baseEntityType = null)")
.AppendLine("{");
+ var className = configurationClassNames[entityType];
using (mainBuilder.Indent())
{
const string entityTypeVariable = "runtimeEntityType";
@@ -577,14 +586,15 @@ private void CreateEntityType(
methodBuilder,
namespaces,
variables,
+ configurationClassNames,
nullable);
Create(entityType, parameters);
- var propertyVariables = new Dictionary();
+ var propertyVariables = new Dictionary();
foreach (var property in entityType.GetDeclaredProperties())
{
- Create(property, propertyVariables, parameters);
+ Create(property, propertyVariables, memberAccessReplacements: null, parameters);
}
foreach (var property in entityType.GetDeclaredServiceProperties())
@@ -804,13 +814,13 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator
private void Create(
IProperty property,
- Dictionary propertyVariables,
+ Dictionary constantReplacements,
+ Dictionary? memberAccessReplacements,
CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
{
var variableName = _code.Identifier(property.Name, parameters.ScopeVariables, capitalize: false);
- propertyVariables[property] = variableName;
- Create(property, variableName, propertyVariables, parameters);
+ Create(property, variableName, constantReplacements, memberAccessReplacements, parameters);
CreateAnnotations(
property,
@@ -823,7 +833,8 @@ private void Create(
private void Create(
IProperty property,
string variableName,
- Dictionary propertyVariables,
+ Dictionary constantReplacements,
+ Dictionary? memberAccessReplacements,
CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
{
var valueGeneratorFactoryType = (Type?)property[CoreAnnotationNames.ValueGeneratorFactoryType];
@@ -841,7 +852,7 @@ private void Create(
.IncrementIndent()
.Append(_code.Literal(property.Name));
- PropertyBaseParameters(property, parameters);
+ GeneratePropertyBaseParameters(property, parameters);
if (property.IsNullable)
{
@@ -982,10 +993,27 @@ private void Create(
.AppendLine(");")
.DecrementIndent();
+ var propertyParameters = parameters with { TargetName = variableName };
+
+ SetPropertyBaseProperties(property, constantReplacements, memberAccessReplacements, propertyParameters);
+
mainBuilder.Append(variableName).Append(".TypeMapping = ");
- _annotationCodeGenerator.Create(property.GetTypeMapping(), property, parameters with { TargetName = variableName });
+ _annotationCodeGenerator.Create(property.GetTypeMapping(), property, propertyParameters);
mainBuilder.AppendLine(";");
+ if (property.IsKey()
+ || property.IsForeignKey()
+ || property.IsUniqueIndex())
+ {
+ var currentComparerType = CurrentValueComparerFactory.Instance.GetComparerType(property);
+ AddNamespace(currentComparerType, parameters.Namespaces);
+
+ mainBuilder
+ .Append(variableName).Append(".SetCurrentValueComparer(new ")
+ .Append(_code.Reference(currentComparerType))
+ .AppendLine($"({variableName}));");
+ }
+
if (sentinel != null
&& converter != null)
{
@@ -995,6 +1023,193 @@ private void Create(
}
}
+ private void
+ SetPropertyBaseProperties(
+ IPropertyBase property,
+ Dictionary? constantReplacements,
+ Dictionary? memberAccessReplacements,
+ CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
+ {
+ var variableName = parameters.TargetName;
+ var mainBuilder = parameters.MainBuilder;
+ constantReplacements ??= [];
+ constantReplacements[property] = variableName;
+ if (!property.IsShadowProperty())
+ {
+ memberAccessReplacements = CreatePrivateAccessors(property, memberAccessReplacements, parameters);
+
+ ClrPropertyGetterFactory.Instance.Create(
+ property,
+ out var getterExpression,
+ out var hasSentinelExpression,
+ out var structuralGetterExpression,
+ out var hasStructuralSentinelExpression);
+
+ mainBuilder
+ .Append(variableName).AppendLine(".SetGetter(")
+ .IncrementIndent()
+ .AppendLines(_code.Expression(getterExpression, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(_code.Expression(hasSentinelExpression, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(_code.Expression(structuralGetterExpression, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(_code.Expression(hasStructuralSentinelExpression, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
+
+ ClrPropertySetterFactory.Instance.Create(property, out var setterExpression);
+
+ mainBuilder
+ .Append(variableName).AppendLine(".SetSetter(")
+ .IncrementIndent()
+ .AppendLines(_code.Expression(setterExpression, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
+
+ ClrPropertyMaterializationSetterFactory.Instance.Create(property, out var materializationSetterExpression);
+
+ mainBuilder
+ .Append(variableName).AppendLine(".SetMaterializationSetter(")
+ .IncrementIndent()
+ .AppendLines(_code.Expression(materializationSetterExpression, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
+
+ PropertyAccessorsFactory.Instance.Create(property,
+ out var currentValueGetter,
+ out var preStoreGeneratedCurrentValueGetter,
+ out var originalValueGetter,
+ out var relationshipSnapshotGetter,
+ out var valueBufferGetter);
+
+ mainBuilder
+ .Append(variableName).AppendLine(".SetAccessors(")
+ .IncrementIndent()
+ .AppendLines(_code.Expression(currentValueGetter, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(_code.Expression(preStoreGeneratedCurrentValueGetter, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(originalValueGetter == null
+ ? "null"
+ : _code.Expression(originalValueGetter, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(_code.Expression(relationshipSnapshotGetter, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(valueBufferGetter == null
+ ? "null"
+ : _code.Expression(valueBufferGetter, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
+ }
+
+ var propertyIndexes = ((IRuntimePropertyBase)property).PropertyIndexes;
+ mainBuilder
+ .Append(variableName).AppendLine(".SetPropertyIndexes(")
+ .IncrementIndent()
+ .Append("index: ").Append(_code.Literal(propertyIndexes.Index)).AppendLine(",")
+ .Append("originalValueIndex: ").Append(_code.Literal(propertyIndexes.OriginalValueIndex)).AppendLine(",")
+ .Append("shadowIndex: ").Append(_code.Literal(propertyIndexes.ShadowIndex)).AppendLine(",")
+ .Append("relationshipIndex: ").Append(_code.Literal(propertyIndexes.RelationshipIndex)).AppendLine(",")
+ .Append("storeGenerationIndex: ").Append(_code.Literal(propertyIndexes.StoreGenerationIndex)).AppendLine(");")
+ .DecrementIndent();
+ }
+
+ private Dictionary? CreatePrivateAccessors(
+ IPropertyBase property,
+ Dictionary? memberAccessReplacements,
+ CSharpRuntimeAnnotationCodeGeneratorParameters parameters,
+ bool create = true,
+ bool qualify = false)
+ {
+ if (property.IsShadowProperty()
+ || property.IsIndexerProperty())
+ {
+ return memberAccessReplacements;
+ }
+
+ memberAccessReplacements = CreatePrivateAccessor(property, forMaterialization: false, forSet: false, create, qualify, memberAccessReplacements, parameters);
+ memberAccessReplacements = CreatePrivateAccessor(property, forMaterialization: false, forSet: true, create, qualify, memberAccessReplacements, parameters);
+ memberAccessReplacements = CreatePrivateAccessor(property, forMaterialization: true, forSet: false, create, qualify, memberAccessReplacements, parameters);
+ memberAccessReplacements = CreatePrivateAccessor(property, forMaterialization: true, forSet: true, create, qualify, memberAccessReplacements, parameters);
+
+ return memberAccessReplacements;
+ }
+
+ private Dictionary? CreatePrivateAccessor(
+ IPropertyBase property,
+ bool forMaterialization,
+ bool forSet,
+ bool create,
+ bool qualify,
+ Dictionary? memberAccessReplacements,
+ CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
+ {
+ var member = property.GetMemberInfo(forMaterialization, forSet);
+ if (member is not FieldInfo field
+ || field.IsPublic)
+ {
+ return memberAccessReplacements;
+ }
+
+ if (memberAccessReplacements?.ContainsKey(new MemberAccess(member, forSet)) != true)
+ {
+ memberAccessReplacements ??= [];
+
+ var methodName = (forSet ? "Write" : "Read") + property.Name;
+ if (create)
+ {
+ var methodBuilder = parameters.MethodBuilder;
+ if (!memberAccessReplacements.ContainsKey(new MemberAccess(member, !forSet)))
+ {
+ AddNamespace(typeof(UnsafeAccessorAttribute), parameters.Namespaces);
+ methodBuilder
+ .AppendLine()
+ .AppendLine($"[UnsafeAccessor(UnsafeAccessorKind.Field, Name = \"{field.Name}\")]")
+ .Append($"extern static ref {_code.Reference(member.GetMemberType())} Get{property.Name}(")
+ .AppendLine($"{_code.Reference(property.DeclaringType.ClrType)} @this);");
+ }
+
+ // Expression trees cannot contain calls to methods that have a ref return, so we need to wrap the call
+ // This approach will not work if the declaring type of the member is a value type
+
+ methodBuilder
+ .AppendLine()
+ .Append($"public static {(forSet ? "void" : _code.Reference(member.GetMemberType()))} {methodName}(")
+ .Append($"{_code.Reference(property.DeclaringType.ClrType)} @this");
+ if (forSet)
+ {
+ methodBuilder
+ .Append($", {_code.Reference(member.GetMemberType())} value");
+ }
+
+ methodBuilder
+ .AppendLine(")")
+ .IncrementIndent()
+ .Append($"=> Get{property.Name}(@this)");
+
+ if (forSet)
+ {
+ methodBuilder
+ .Append(" = value");
+ }
+
+ methodBuilder
+ .AppendLine(";")
+ .DecrementIndent();
+ }
+
+ if (qualify)
+ {
+ methodName = parameters.ConfigurationClassNames[property.DeclaringType] + "." + methodName;
+ }
+
+ memberAccessReplacements.Add(new MemberAccess(field, forSet), methodName);
+ }
+
+ return memberAccessReplacements;
+ }
+
private static Type? GetValueConverterType(IProperty property)
{
var annotation = property.FindAnnotation(CoreAnnotationNames.ValueConverterType);
@@ -1004,7 +1219,7 @@ private void Create(
.ValueConverterType;
}
- private void PropertyBaseParameters(
+ private void GeneratePropertyBaseParameters(
IPropertyBase property,
CSharpRuntimeAnnotationCodeGeneratorParameters parameters,
bool skipType = false)
@@ -1077,7 +1292,7 @@ private void FindProperties(
IEnumerable properties,
IndentedStringBuilder mainBuilder,
bool nullable,
- Dictionary? propertyVariables = null)
+ Dictionary? propertyVariables = null)
{
mainBuilder.Append("new[] { ");
var first = true;
@@ -1128,7 +1343,7 @@ private void Create(
.IncrementIndent()
.Append(_code.Literal(property.Name));
- PropertyBaseParameters(property, parameters, skipType: true);
+ GeneratePropertyBaseParameters(property, parameters, skipType: true);
AddNamespace(property.ClrType, parameters.Namespaces);
mainBuilder
@@ -1139,17 +1354,18 @@ private void Create(
.AppendLine(");")
.DecrementIndent();
- CreateAnnotations(
- property,
- _annotationCodeGenerator.Generate,
- parameters with { TargetName = variableName });
+ var propertyParameters = parameters with { TargetName = variableName };
+
+ // Service properties don't use property accessors, so don't generate them
+
+ CreateAnnotations(property, _annotationCodeGenerator.Generate, propertyParameters);
mainBuilder.AppendLine();
}
private void Create(
IKey key,
- Dictionary propertyVariables,
+ Dictionary propertyVariables,
CSharpRuntimeAnnotationCodeGeneratorParameters parameters,
bool nullable)
{
@@ -1183,7 +1399,7 @@ private void Create(
private void Create(
IIndex index,
- Dictionary propertyVariables,
+ Dictionary propertyVariables,
CSharpRuntimeAnnotationCodeGeneratorParameters parameters,
bool nullable)
{
@@ -1227,17 +1443,21 @@ private void CreateComplexProperty(
IndentedStringBuilder mainBuilder,
IndentedStringBuilder methodBuilder,
SortedSet namespaces,
+ Dictionary configurationClassNames,
string topClassName,
bool nullable)
{
+ var className = _code.Identifier(complexProperty.Name, capitalize: true);
mainBuilder
.AppendLine()
- .Append("private static class ")
- .Append(_code.Identifier(complexProperty.Name, capitalize: true))
+ .Append("public static class ")
+ .Append(className)
.AppendLine("ComplexProperty")
.AppendLine("{");
+ methodBuilder = new IndentedStringBuilder();
var complexType = complexProperty.ComplexType;
+ configurationClassNames[complexType] = configurationClassNames[complexProperty.DeclaringType] + "." + className;
using (mainBuilder.Indent())
{
var declaringTypeVariable = "declaringType";
@@ -1281,9 +1501,10 @@ private void CreateComplexProperty(
methodBuilder,
namespaces,
variables,
+ configurationClassNames,
nullable);
- PropertyBaseParameters(complexProperty, parameters, skipType: true);
+ GeneratePropertyBaseParameters(complexProperty, parameters, skipType: true);
if (complexProperty.IsNullable)
{
@@ -1347,10 +1568,21 @@ private void CreateComplexProperty(
.Append(complexPropertyVariable).AppendLine(".ComplexType;");
var complexTypeParameters = parameters with { TargetName = complexTypeVariable };
- var propertyVariables = new Dictionary();
+ var complexPropertyParameters = parameters with { TargetName = complexPropertyVariable };
+
+ var constantReplacements = new Dictionary();
+ Dictionary? memberAccessReplacements = null;
+
+ foreach (var chainedComplexProperty in complexProperty.GetChainToComplexProperty())
+ {
+ memberAccessReplacements = CreatePrivateAccessors(chainedComplexProperty, memberAccessReplacements, complexTypeParameters, create: chainedComplexProperty == complexProperty);
+ }
+
+ SetPropertyBaseProperties(complexProperty, constantReplacements, memberAccessReplacements, complexPropertyParameters);
+
foreach (var property in complexType.GetProperties())
{
- Create(property, propertyVariables, complexTypeParameters);
+ Create(property, constantReplacements, memberAccessReplacements, complexTypeParameters);
}
foreach (var nestedComplexProperty in complexType.GetComplexProperties())
@@ -1364,15 +1596,8 @@ private void CreateComplexProperty(
.AppendLine(");");
}
- CreateAnnotations(
- complexType,
- _annotationCodeGenerator.Generate,
- complexTypeParameters);
-
- CreateAnnotations(
- complexProperty,
- _annotationCodeGenerator.Generate,
- parameters with { TargetName = complexPropertyVariable });
+ CreateAnnotations(complexType, _annotationCodeGenerator.Generate, complexTypeParameters);
+ CreateAnnotations(complexProperty, _annotationCodeGenerator.Generate, complexPropertyParameters);
mainBuilder
.Append("return ")
@@ -1387,10 +1612,16 @@ private void CreateComplexProperty(
{
foreach (var nestedComplexProperty in complexType.GetComplexProperties())
{
- CreateComplexProperty(nestedComplexProperty, mainBuilder, methodBuilder, namespaces, topClassName, nullable);
+ CreateComplexProperty(nestedComplexProperty, mainBuilder, methodBuilder, namespaces, configurationClassNames, topClassName, nullable);
}
}
+ var methods = methodBuilder.ToString();
+ if (!string.IsNullOrEmpty(methods))
+ {
+ mainBuilder.AppendLines(methods);
+ }
+
mainBuilder.AppendLine("}");
}
@@ -1400,6 +1631,7 @@ private void CreateForeignKey(
IndentedStringBuilder mainBuilder,
IndentedStringBuilder methodBuilder,
SortedSet namespaces,
+ Dictionary configurationClassNames,
string className,
bool nullable)
{
@@ -1487,6 +1719,7 @@ private void CreateForeignKey(
methodBuilder,
namespaces,
variables,
+ configurationClassNames,
nullable);
var navigation = foreignKey.DependentToPrincipal;
@@ -1530,7 +1763,7 @@ private void Create(
.Append(foreignKeyVariable).AppendLine(",")
.Append("onDependent: ").Append(_code.Literal(navigation.IsOnDependent));
- PropertyBaseParameters(navigation, parameters);
+ GeneratePropertyBaseParameters(navigation, parameters);
if (navigation.IsEagerLoaded)
{
@@ -1549,10 +1782,66 @@ private void Create(
.AppendLine()
.DecrementIndent();
- CreateAnnotations(
+ var navigationParameters = parameters with { TargetName = navigationVariable };
+
+ var constantReplacements = new Dictionary();
+ var memberAccessReplacements = CreatePrivateAccessors(navigation, null, navigationParameters, create: false, qualify: true);
+ SetPropertyBaseProperties(navigation, constantReplacements, memberAccessReplacements, navigationParameters);
+
+ SetNavigationBaseProperties(navigation, constantReplacements, memberAccessReplacements, navigationParameters);
+
+ CreateAnnotations(navigation, _annotationCodeGenerator.Generate, navigationParameters);
+ }
+
+ private void SetNavigationBaseProperties(
+ INavigationBase navigation,
+ Dictionary constantReplacements,
+ Dictionary? memberAccessReplacements,
+ CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
+ {
+ if (!navigation.IsCollection)
+ {
+ return;
+ }
+
+ var mainBuilder = parameters.MainBuilder;
+ ClrCollectionAccessorFactory.Instance.Create(
navigation,
- _annotationCodeGenerator.Generate,
- parameters with { TargetName = navigationVariable });
+ out var entityType,
+ out var propertyType,
+ out var elementType,
+ out var getCollection,
+ out var setCollection,
+ out var setCollectionForMaterialization,
+ out var createAndSetCollection,
+ out var createCollection);
+
+ AddNamespace(propertyType, parameters.Namespaces);
+ mainBuilder
+ .Append(parameters.TargetName)
+ .AppendLine($".SetCollectionAccessor<{_code.Reference(entityType)}, {_code.Reference(propertyType)}, {_code.Reference(elementType)}>(")
+ .IncrementIndent()
+ .AppendLines(getCollection == null
+ ? "null"
+ : _code.Expression(getCollection, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(setCollection == null
+ ? "null"
+ : _code.Expression(setCollection, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(setCollectionForMaterialization == null
+ ? "null"
+ : _code.Expression(setCollectionForMaterialization, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(createAndSetCollection == null
+ ? "null"
+ : _code.Expression(createAndSetCollection, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(",")
+ .AppendLines(createCollection == null
+ ? "null"
+ : _code.Expression(createCollection, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
}
private void CreateSkipNavigation(
@@ -1561,6 +1850,7 @@ private void CreateSkipNavigation(
IndentedStringBuilder mainBuilder,
IndentedStringBuilder methodBuilder,
SortedSet namespaces,
+ Dictionary configurationClassNames,
string className,
bool nullable)
{
@@ -1593,6 +1883,7 @@ private void CreateSkipNavigation(
methodBuilder,
namespaces,
variables,
+ configurationClassNames,
nullable);
mainBuilder
@@ -1625,7 +1916,7 @@ private void CreateSkipNavigation(
.Append(_code.Literal(navigation.IsCollection)).AppendLine(",")
.Append(_code.Literal(navigation.IsOnDependent));
- PropertyBaseParameters(navigation, parameters with { TargetName = declaringEntityType });
+ GeneratePropertyBaseParameters(navigation, parameters with { TargetName = declaringEntityType });
if (navigation.IsEagerLoaded)
{
@@ -1662,10 +1953,13 @@ private void CreateSkipNavigation(
.AppendLine("}")
.AppendLine();
- CreateAnnotations(
- navigation,
- _annotationCodeGenerator.Generate,
- parameters);
+ var constantReplacements = new Dictionary();
+ var memberAccessReplacements = new Dictionary();
+ SetPropertyBaseProperties(navigation, constantReplacements, memberAccessReplacements, parameters);
+
+ SetNavigationBaseProperties(navigation, constantReplacements, memberAccessReplacements, parameters);
+
+ CreateAnnotations(navigation, _annotationCodeGenerator.Generate, parameters);
mainBuilder
.Append("return ")
@@ -1702,7 +1996,7 @@ private void CreateAnnotations(
IndentedStringBuilder mainBuilder,
IndentedStringBuilder methodBuilder,
SortedSet namespaces,
- string className,
+ Dictionary configurationClassNames,
bool nullable)
{
mainBuilder.AppendLine()
@@ -1710,22 +2004,114 @@ private void CreateAnnotations(
.AppendLine("(RuntimeEntityType runtimeEntityType)")
.AppendLine("{");
+ var className = configurationClassNames[entityType];
using (mainBuilder.Indent())
{
const string entityTypeVariable = "runtimeEntityType";
var variables = new HashSet { entityTypeVariable };
- CreateAnnotations(
- entityType,
- _annotationCodeGenerator.Generate,
- new CSharpRuntimeAnnotationCodeGeneratorParameters(
+ var parameters = new CSharpRuntimeAnnotationCodeGeneratorParameters(
entityTypeVariable,
className,
mainBuilder,
methodBuilder,
namespaces,
variables,
- nullable));
+ configurationClassNames,
+ nullable);
+
+ var constantReplacements = new Dictionary() { { entityType, parameters.TargetName } };
+
+ var baseType = entityType.BaseType;
+ while (baseType != null)
+ {
+ if (!constantReplacements.ContainsKey(baseType))
+ {
+ constantReplacements[baseType] = parameters.TargetName;
+ }
+ baseType = baseType.BaseType;
+ }
+
+ Dictionary? memberAccessReplacements = null;
+ memberAccessReplacements = GenerateMemberReferences(entityType, constantReplacements, memberAccessReplacements, parameters);
+
+ foreach (var navigation in entityType.GetNavigations())
+ {
+ var variableName = _code.Identifier(navigation.Name, parameters.ScopeVariables, capitalize: false);
+ constantReplacements[navigation] = variableName;
+
+ memberAccessReplacements = CreatePrivateAccessors(navigation, memberAccessReplacements, parameters, create: navigation.DeclaringType == entityType, qualify: navigation.DeclaringType != entityType);
+
+ mainBuilder
+ .Append($"var {variableName} = ")
+ .AppendLine($"{parameters.TargetName}.FindNavigation({_code.Literal(navigation.Name)})!;");
+ }
+
+ var runtimeType = (IRuntimeEntityType)entityType;
+
+ var originalValuesFactory = OriginalValuesFactoryFactory.Instance.CreateExpression(runtimeType);
+ mainBuilder
+ .Append(parameters.TargetName).AppendLine(".SetOriginalValuesFactory(")
+ .IncrementIndent()
+ .AppendLines(_code.Expression(originalValuesFactory, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
+
+ var storeGeneratedValuesFactory = StoreGeneratedValuesFactoryFactory.Instance.CreateEmptyExpression(runtimeType);
+ mainBuilder
+ .Append(parameters.TargetName).AppendLine(".SetStoreGeneratedValuesFactory(")
+ .IncrementIndent()
+ .AppendLines(_code.Expression(storeGeneratedValuesFactory, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
+
+ var temporaryValuesFactory = TemporaryValuesFactoryFactory.Instance.CreateExpression(runtimeType);
+ mainBuilder
+ .Append(parameters.TargetName).AppendLine(".SetTemporaryValuesFactory(")
+ .IncrementIndent()
+ .AppendLines(_code.Expression(temporaryValuesFactory, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
+
+ var shadowValuesFactory = ShadowValuesFactoryFactory.Instance.CreateExpression(runtimeType);
+ mainBuilder
+ .Append(parameters.TargetName).AppendLine(".SetShadowValuesFactory(")
+ .IncrementIndent()
+ .AppendLines(_code.Expression(shadowValuesFactory, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
+
+ var emptyShadowValuesFactory = EmptyShadowValuesFactoryFactory.Instance.CreateEmptyExpression(runtimeType);
+ mainBuilder
+ .Append(parameters.TargetName).AppendLine(".SetEmptyShadowValuesFactory(")
+ .IncrementIndent()
+ .AppendLines(_code.Expression(emptyShadowValuesFactory, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
+
+ var relationshipSnapshotFactory = RelationshipSnapshotFactoryFactory.Instance.CreateExpression(runtimeType);
+ mainBuilder
+ .Append(parameters.TargetName).AppendLine(".SetRelationshipSnapshotFactory(")
+ .IncrementIndent()
+ .AppendLines(_code.Expression(relationshipSnapshotFactory, constantReplacements, memberAccessReplacements, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLine(");")
+ .DecrementIndent();
+
+ AddNamespace(typeof(PropertyCounts), parameters.Namespaces);
+ var counts = runtimeType.Counts;
+ mainBuilder
+ .Append(parameters.TargetName).AppendLine(".Counts = new PropertyCounts(")
+ .IncrementIndent()
+ .Append("propertyCount: ").Append(_code.Literal(counts.PropertyCount)).AppendLine(",")
+ .Append("navigationCount: ").Append(_code.Literal(counts.NavigationCount)).AppendLine(",")
+ .Append("complexPropertyCount: ").Append(_code.Literal(counts.ComplexPropertyCount)).AppendLine(",")
+ .Append("originalValueCount: ").Append(_code.Literal(counts.OriginalValueCount)).AppendLine(",")
+ .Append("shadowCount: ").Append(_code.Literal(counts.ShadowCount)).AppendLine(",")
+ .Append("relationshipCount: ").Append(_code.Literal(counts.RelationshipCount)).AppendLine(",")
+ .Append("storeGeneratedCount: ").Append(_code.Literal(counts.StoreGeneratedCount)).AppendLine(");")
+ .DecrementIndent();
+
+ CreateAnnotations(entityType, _annotationCodeGenerator.Generate, parameters);
mainBuilder
.AppendLine()
@@ -1736,6 +2122,52 @@ private void CreateAnnotations(
.AppendLine("}")
.AppendLine()
.AppendLine("static partial void Customize(RuntimeEntityType runtimeEntityType);");
+
+ Dictionary? GenerateMemberReferences(
+ ITypeBase structuralType,
+ Dictionary constantReplacements,
+ Dictionary? memberAccessReplacements,
+ CSharpRuntimeAnnotationCodeGeneratorParameters parameters,
+ bool nested = false)
+ {
+ var mainBuilder = parameters.MainBuilder;
+ foreach (var property in structuralType.GetProperties())
+ {
+ var variableName = _code.Identifier(property.Name, parameters.ScopeVariables, capitalize: false);
+ constantReplacements[property] = variableName;
+
+ memberAccessReplacements = CreatePrivateAccessors(
+ property, memberAccessReplacements, parameters, create: false, qualify: nested || property.DeclaringType != structuralType);
+
+ mainBuilder
+ .Append($"var {variableName} = ")
+ .AppendLine($"{constantReplacements[property.DeclaringType]}.FindProperty({_code.Literal(property.Name)})!;");
+ }
+
+ foreach (var complexProperty in structuralType.GetComplexProperties())
+ {
+ var variableName = _code.Identifier(complexProperty.Name, parameters.ScopeVariables, capitalize: false);
+ constantReplacements[complexProperty] = variableName;
+
+ memberAccessReplacements = CreatePrivateAccessors(
+ complexProperty, memberAccessReplacements, parameters, create: false, qualify: nested || complexProperty.DeclaringType != structuralType);
+
+ mainBuilder
+ .Append($"var {variableName} = ")
+ .AppendLine($"{constantReplacements[complexProperty.DeclaringType]}.FindComplexProperty({_code.Literal(complexProperty.Name)})!;");
+
+ var typeVariableName = _code.Identifier(complexProperty.ComplexType.ShortName(), parameters.ScopeVariables, capitalize: false);
+ constantReplacements[complexProperty.ComplexType] = typeVariableName;
+
+ mainBuilder
+ .Append($"var {typeVariableName} = ")
+ .AppendLine($"{variableName}.ComplexType;");
+
+ GenerateMemberReferences(complexProperty.ComplexType, constantReplacements, memberAccessReplacements, parameters, nested: true);
+ }
+
+ return memberAccessReplacements;
+ }
}
private static void CreateAnnotations(
@@ -1752,7 +2184,8 @@ private static void CreateAnnotations(
annotatable,
parameters with
{
- Annotations = annotatable.GetRuntimeAnnotations().ToDictionary(a => a.Name, a => a.Value), IsRuntime = true
+ Annotations = annotatable.GetRuntimeAnnotations().ToDictionary(a => a.Name, a => a.Value),
+ IsRuntime = true
});
}
diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs
index b013b53e209..bfd2c130352 100644
--- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs
+++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs
@@ -55,8 +55,7 @@ public override void Generate(IModel model, CSharpRuntimeAnnotationCodeGenerator
var methods = methodBuilder.ToString();
if (!string.IsNullOrEmpty(methods))
{
- parameters.MethodBuilder.AppendLine()
- .AppendLines(methods);
+ parameters.MethodBuilder.AppendLines(methods);
}
}
}
diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs
index 2bbc36a629a..01dbeb99eb6 100644
--- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs
@@ -2034,10 +2034,6 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
private sealed class ValueBufferTryReadValueMethodsReplacer : ExpressionVisitor
{
- private static readonly MethodInfo PopulateListMethod
- = typeof(ValueBufferTryReadValueMethodsReplacer).GetMethod(
- nameof(PopulateList), BindingFlags.NonPublic | BindingFlags.Static)!;
-
private readonly Expression _instance;
private readonly Dictionary _propertyAssignmentMap;
@@ -2057,7 +2053,14 @@ protected override Expression VisitBinary(BinaryExpression node)
if (property!.IsPrimitiveCollection
&& !property.ClrType.IsArray)
{
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ var genericMethod = EntityMaterializerSource.PopulateListMethod.MakeGenericMethod(
+ property.ClrType.TryGetElementType(typeof(IEnumerable<>))!);
+#pragma warning restore EF1001 // Internal EF Core API usage.
var currentVariable = Variable(parameter!.Type);
+ var convertedVariable = genericMethod.GetParameters()[1].ParameterType.IsAssignableFrom(currentVariable.Type)
+ ? (Expression)currentVariable
+ : Convert(currentVariable, genericMethod.GetParameters()[1].ParameterType);
return Block(
new[] { currentVariable },
MakeMemberAccess(_instance, property.GetMemberInfo(forMaterialization: true, forSet: false))
@@ -2070,9 +2073,9 @@ protected override Expression VisitBinary(BinaryExpression node)
? leftMemberExpression.Assign(parameter)
: MakeBinary(node.NodeType, node.Left, parameter),
Call(
- PopulateListMethod.MakeGenericMethod(property.ClrType.TryGetElementType(typeof(IEnumerable<>))!),
+ genericMethod,
parameter,
- currentVariable)
+ convertedVariable)
));
}
@@ -2111,17 +2114,6 @@ private bool IsPropertyAssignment(
parameter = null;
return false;
}
-
- private static IList PopulateList(IList buffer, IList target)
- {
- target.Clear();
- foreach (var value in buffer)
- {
- target.Add(value);
- }
-
- return target;
- }
}
}
diff --git a/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs b/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs
index 111c6e46e6a..aef1710abec 100644
--- a/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/CompositeValueFactory.cs
@@ -155,19 +155,15 @@ public virtual object CreatePrincipalEquatableKey(IUpdateEntry entry, bool fromO
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
protected static IEqualityComparer> CreateEqualityComparer(IReadOnlyList properties)
- => new CompositeCustomComparer(properties.Select(p => p.GetKeyValueComparer()).ToList());
+ => new CompositeCustomComparer(properties.Select(p => p.GetKeyValueComparer()).ToArray());
private sealed class CompositeCustomComparer : IEqualityComparer>
{
- private readonly int _valueCount;
- private readonly Func[] _equals;
- private readonly Func[] _hashCodes;
+ private readonly ValueComparer[] _comparers;
- public CompositeCustomComparer(IList comparers)
+ public CompositeCustomComparer(ValueComparer[] comparers)
{
- _valueCount = comparers.Count;
- _equals = comparers.Select(c => (Func)c.Equals).ToArray();
- _hashCodes = comparers.Select(c => (Func)c.GetHashCode).ToArray();
+ _comparers = comparers;
}
public bool Equals(IReadOnlyList? x, IReadOnlyList? y)
@@ -187,15 +183,15 @@ public bool Equals(IReadOnlyList? x, IReadOnlyList? y)
return false;
}
- if (x.Count != _valueCount
- || y.Count != _valueCount)
+ if (x.Count != _comparers.Length
+ || y.Count != _comparers.Length)
{
return false;
}
- for (var i = 0; i < _valueCount; i++)
+ for (var i = 0; i < _comparers.Length; i++)
{
- if (!_equals[i](x[i], y[i]))
+ if (!_comparers[i].Equals(x[i], y[i]))
{
return false;
}
@@ -212,7 +208,7 @@ public int GetHashCode(IReadOnlyList obj)
// ReSharper disable once LoopCanBeConvertedToQuery
for (var i = 0; i < obj.Count; i++)
{
- hashCode.Add(_hashCodes[i](obj[i]));
+ hashCode.Add(_comparers[i].GetHashCode(obj[i]));
}
return hashCode.ToHashCode();
diff --git a/src/EFCore/ChangeTracking/Internal/CurrentProviderValueComparer`.cs b/src/EFCore/ChangeTracking/Internal/CurrentProviderValueComparer`.cs
index c7761eb8538..3a0e921ac46 100644
--- a/src/EFCore/ChangeTracking/Internal/CurrentProviderValueComparer`.cs
+++ b/src/EFCore/ChangeTracking/Internal/CurrentProviderValueComparer`.cs
@@ -21,12 +21,10 @@ public class CurrentProviderValueComparer : IComparer
- public CurrentProviderValueComparer(
- IPropertyBase property,
- ValueConverter converter)
+ public CurrentProviderValueComparer(IProperty property)
{
_property = property;
- _converter = converter.ConvertToProviderExpression.Compile();
+ _converter = ((ValueConverter)property.GetTypeMapping().Converter!).ConvertToProviderTyped;
_underlyingComparer = Comparer.Default;
}
diff --git a/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs b/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs
index ab29fa3d622..f6c8760e9a5 100644
--- a/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/CurrentValueComparerFactory.cs
@@ -13,6 +13,18 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
///
public class CurrentValueComparerFactory
{
+ private CurrentValueComparerFactory()
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static readonly CurrentValueComparerFactory Instance = new();
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -20,24 +32,31 @@ public class CurrentValueComparerFactory
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual IComparer Create(IProperty property)
+ => (IComparer)Activator.CreateInstance(GetComparerType(property), property)!;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual Type GetComparerType(IProperty property)
{
var modelType = property.ClrType;
var nonNullableModelType = modelType.UnwrapNullableType();
if (IsGenericComparable(modelType, nonNullableModelType))
{
- return (IComparer)Activator.CreateInstance(
- typeof(EntryCurrentValueComparer<>).MakeGenericType(modelType),
- property)!;
+ return typeof(EntryCurrentValueComparer<>).MakeGenericType(modelType);
}
if (typeof(IStructuralComparable).IsAssignableFrom(nonNullableModelType))
{
- return new StructuralEntryCurrentValueComparer(property);
+ return typeof(StructuralEntryCurrentValueComparer);
}
if (typeof(IComparable).IsAssignableFrom(nonNullableModelType))
{
- return new EntryCurrentValueComparer(property);
+ return typeof(EntryCurrentValueComparer);
}
var converter = property.GetTypeMapping().Converter;
@@ -51,24 +70,24 @@ public virtual IComparer Create(IProperty property)
var modelBaseType = elementType != null
? typeof(IEnumerable<>).MakeGenericType(elementType.ClrType)
: modelType;
- var comparerType = modelType.IsClass
+ var comparerType = !modelType.IsValueType
? typeof(NullableClassCurrentProviderValueComparer<,>).MakeGenericType(modelBaseType, providerType)
: modelType == converter.ModelClrType
? typeof(CurrentProviderValueComparer<,>).MakeGenericType(modelBaseType, providerType)
: typeof(NullableStructCurrentProviderValueComparer<,>).MakeGenericType(
nonNullableModelType, providerType);
- return (IComparer)Activator.CreateInstance(comparerType, property, converter)!;
+ return comparerType;
}
if (typeof(IStructuralComparable).IsAssignableFrom(nonNullableProviderType))
{
- return new StructuralEntryCurrentProviderValueComparer(property, converter);
+ return typeof(StructuralEntryCurrentProviderValueComparer);
}
if (typeof(IComparable).IsAssignableFrom(nonNullableProviderType))
{
- return new EntryCurrentProviderValueComparer(property, converter);
+ return typeof(EntryCurrentProviderValueComparer);
}
throw new InvalidOperationException(
diff --git a/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs
index 1771645c961..06497d8b090 100644
--- a/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs
@@ -52,6 +52,15 @@ protected override int GetPropertyCount(IRuntimeEntityType entityType)
protected override ValueComparer? GetValueComparer(IProperty property)
=> null;
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override MethodInfo? GetValueComparerMethod()
+ => null;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore/ChangeTracking/Internal/EntryCurrentProviderValueComparer.cs b/src/EFCore/ChangeTracking/Internal/EntryCurrentProviderValueComparer.cs
index 2d095cb7cae..8459ef53f96 100644
--- a/src/EFCore/ChangeTracking/Internal/EntryCurrentProviderValueComparer.cs
+++ b/src/EFCore/ChangeTracking/Internal/EntryCurrentProviderValueComparer.cs
@@ -19,12 +19,10 @@ public class EntryCurrentProviderValueComparer : EntryCurrentValueComparer
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public EntryCurrentProviderValueComparer(
- IProperty property,
- ValueConverter converter)
+ public EntryCurrentProviderValueComparer(IProperty property)
: base(property)
{
- _converter = converter;
+ _converter = property.GetTypeMapping().Converter!;
}
///
diff --git a/src/EFCore/ChangeTracking/Internal/IdentityMapFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/IdentityMapFactoryFactory.cs
index 7e103190993..df18e8b9bb1 100644
--- a/src/EFCore/ChangeTracking/Internal/IdentityMapFactoryFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/IdentityMapFactoryFactory.cs
@@ -20,11 +20,13 @@ public class IdentityMapFactoryFactory
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual Func Create(IKey key)
- => (Func)typeof(IdentityMapFactoryFactory)
- .GetMethod(nameof(CreateFactory), BindingFlags.NonPublic | BindingFlags.Static)!
+ => (Func)GenericCreateFactory
.MakeGenericMethod(key.GetKeyType())
.Invoke(null, [key])!;
+ private static readonly MethodInfo GenericCreateFactory
+ = typeof(IdentityMapFactoryFactory).GetMethod(nameof(CreateFactory), BindingFlags.Static | BindingFlags.NonPublic)!;
+
[UsedImplicitly]
private static Func CreateFactory(IKey key)
where TKey : notnull
diff --git a/src/EFCore/ChangeTracking/Internal/NullableClassCurrentProviderValueComparer.cs b/src/EFCore/ChangeTracking/Internal/NullableClassCurrentProviderValueComparer.cs
index 39f4821733a..d69b9108e06 100644
--- a/src/EFCore/ChangeTracking/Internal/NullableClassCurrentProviderValueComparer.cs
+++ b/src/EFCore/ChangeTracking/Internal/NullableClassCurrentProviderValueComparer.cs
@@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
public class NullableClassCurrentProviderValueComparer : IComparer
where TModel : class
{
- private readonly IPropertyBase _property;
+ private readonly IProperty _property;
private readonly IComparer _underlyingComparer;
private readonly Func _converter;
@@ -22,12 +22,10 @@ public class NullableClassCurrentProviderValueComparer : ICom
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public NullableClassCurrentProviderValueComparer(
- IPropertyBase property,
- ValueConverter converter)
+ public NullableClassCurrentProviderValueComparer(IProperty property)
{
_property = property;
- _converter = converter.ConvertToProviderExpression.Compile();
+ _converter = ((ValueConverter)property.GetTypeMapping().Converter!).ConvertToProviderTyped;
_underlyingComparer = Comparer.Default;
}
diff --git a/src/EFCore/ChangeTracking/Internal/NullableCurrentProviderValueComparer.cs b/src/EFCore/ChangeTracking/Internal/NullableCurrentProviderValueComparer.cs
index 4ee9fbf11a2..765f6766a59 100644
--- a/src/EFCore/ChangeTracking/Internal/NullableCurrentProviderValueComparer.cs
+++ b/src/EFCore/ChangeTracking/Internal/NullableCurrentProviderValueComparer.cs
@@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
public class NullableStructCurrentProviderValueComparer : IComparer
where TModel : struct
{
- private readonly IPropertyBase _property;
+ private readonly IProperty _property;
private readonly IComparer _underlyingComparer;
private readonly Func _converter;
@@ -22,12 +22,10 @@ public class NullableStructCurrentProviderValueComparer : ICo
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public NullableStructCurrentProviderValueComparer(
- IPropertyBase property,
- ValueConverter converter)
+ public NullableStructCurrentProviderValueComparer(IProperty property)
{
_property = property;
- _converter = converter.ConvertToProviderExpression.Compile();
+ _converter = ((ValueConverter)property.GetTypeMapping().Converter!).ConvertToProviderTyped;
_underlyingComparer = Comparer.Default;
}
diff --git a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs
index 5f7c67314f2..23bdf30c55f 100644
--- a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs
@@ -13,6 +13,9 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
///
public class OriginalValuesFactoryFactory : SnapshotFactoryFactory
{
+ private static readonly MethodInfo _getValueComparerMethod
+ = typeof(IProperty).GetMethod(nameof(IProperty.GetValueComparer))!;
+
private OriginalValuesFactoryFactory()
{
}
@@ -51,4 +54,13 @@ protected override int GetPropertyCount(IRuntimeEntityType entityType)
///
protected override ValueComparer? GetValueComparer(IProperty property)
=> property.GetValueComparer();
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override MethodInfo? GetValueComparerMethod()
+ => _getValueComparerMethod;
}
diff --git a/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs
index 4c7b7b5d8b1..2557ec81cce 100644
--- a/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs
@@ -13,6 +13,9 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
///
public class RelationshipSnapshotFactoryFactory : SnapshotFactoryFactory
{
+ private static readonly MethodInfo _getValueComparerMethod
+ = typeof(IProperty).GetMethod(nameof(IProperty.GetKeyValueComparer))!;
+
private RelationshipSnapshotFactoryFactory()
{
}
@@ -51,4 +54,13 @@ protected override int GetPropertyCount(IRuntimeEntityType entityType)
///
protected override ValueComparer? GetValueComparer(IProperty property)
=> property.GetKeyValueComparer();
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override MethodInfo? GetValueComparerMethod()
+ => _getValueComparerMethod;
}
diff --git a/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs
index f15f073578c..9b845c7edbb 100644
--- a/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs
@@ -52,6 +52,15 @@ protected override int GetPropertyCount(IRuntimeEntityType entityType)
protected override ValueComparer? GetValueComparer(IProperty property)
=> null;
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override MethodInfo? GetValueComparerMethod()
+ => null;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -61,9 +70,6 @@ protected override int GetPropertyCount(IRuntimeEntityType entityType)
protected override bool UseEntityVariable
=> false;
- internal static readonly MethodInfo ContainsKeyMethod =
- typeof(IDictionary).GetMethod(nameof(IDictionary.ContainsKey), [typeof(string)])!;
-
private static readonly PropertyInfo DictionaryIndexer
= typeof(IDictionary).GetRuntimeProperties().Single(p => p.GetIndexParameters().Length > 0);
@@ -93,7 +99,7 @@ protected override Expression CreateReadShadowValueExpression(
property.ClrType);
}
- return Expression.Condition(Expression.Call(parameter, ContainsKeyMethod, Expression.Constant(property.Name)),
+ return Expression.Condition(Expression.Call(parameter, PropertyAccessorsFactory.ContainsKeyMethod, Expression.Constant(property.Name)),
Expression.Convert(Expression.MakeIndex(
parameter,
DictionaryIndexer,
diff --git a/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs
index 728fc48d83e..6cce1abafd5 100644
--- a/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs
@@ -13,6 +13,9 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
///
public class SidecarValuesFactoryFactory : SnapshotFactoryFactory
{
+ private static readonly MethodInfo _getValueComparerMethod
+ = typeof(IProperty).GetMethod(nameof(IProperty.GetValueComparer))!;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -57,4 +60,13 @@ protected override int GetPropertyCount(IRuntimeEntityType entityType)
///
protected override ValueComparer? GetValueComparer(IProperty property)
=> property.GetValueComparer();
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override MethodInfo? GetValueComparerMethod()
+ => _getValueComparerMethod;
}
diff --git a/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs b/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs
index 71778671a49..43b782066a0 100644
--- a/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/SimplePrincipalKeyValueFactory.cs
@@ -30,7 +30,7 @@ public SimplePrincipalKeyValueFactory(IKey key)
_property = key.Properties.Single();
_propertyAccessors = _property.GetPropertyAccessors();
- EqualityComparer = new NoNullsCustomEqualityComparer(_property.GetKeyValueComparer());
+ EqualityComparer = new NoNullsCustomEqualityComparer((ValueComparer)_property.GetKeyValueComparer());
}
///
@@ -121,19 +121,17 @@ public virtual object CreateEquatableKey(IUpdateEntry entry, bool fromOriginalVa
private sealed class NoNullsCustomEqualityComparer : IEqualityComparer
{
- private readonly Func _equals;
- private readonly Func _hashCode;
+ private readonly ValueComparer _comparer;
- public NoNullsCustomEqualityComparer(ValueComparer comparer)
+ public NoNullsCustomEqualityComparer(ValueComparer comparer)
{
- _equals = (Func)comparer.EqualsExpression.Compile();
- _hashCode = (Func)comparer.HashCodeExpression.Compile();
+ _comparer = comparer;
}
public bool Equals(TKey? x, TKey? y)
- => _equals(x, y);
+ => _comparer.Equals(x, y);
public int GetHashCode([DisallowNull] TKey obj)
- => _hashCode(obj);
+ => _comparer.GetHashCode(obj);
}
}
diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs
index 127deb27422..d7cae14cec3 100644
--- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using JetBrains.Annotations;
+using System.Reflection.Metadata;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -141,7 +141,7 @@ protected virtual Expression CreateSnapshotExpression(
}
var memberInfo = propertyBase.GetMemberInfo(forMaterialization: false, forSet: false);
- var memberAccess = PropertyBase.CreateMemberAccess(propertyBase, entityVariable!, memberInfo, fromContainingType: false);
+ var memberAccess = PropertyAccessorsFactory.CreateMemberAccess(propertyBase, entityVariable!, memberInfo, fromContainingType: false);
if (memberAccess.Type != propertyBase.ClrType)
{
@@ -200,10 +200,16 @@ private Expression CreateSnapshotValueExpression(Expression expression, IPropert
expression = Expression.Convert(expression, comparer.Type);
}
- var snapshotExpression = ReplacingExpressionVisitor.Replace(
- comparer.SnapshotExpression.Parameters.Single(),
- expression,
- comparer.SnapshotExpression.Body);
+ var comparerExpression = Expression.Convert(
+ Expression.Call(
+ Expression.Constant(property),
+ GetValueComparerMethod()!),
+ typeof(ValueComparer<>).MakeGenericType(comparer.Type));
+
+ Expression snapshotExpression = Expression.Call(
+ comparerExpression,
+ ValueComparer.GetGenericSnapshotMethod(comparer.Type),
+ expression);
if (snapshotExpression.Type != propertyBase.ClrType)
{
@@ -228,6 +234,14 @@ private Expression CreateSnapshotValueExpression(Expression expression, IPropert
///
protected abstract ValueComparer? GetValueComparer(IProperty property);
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected abstract MethodInfo? GetValueComparerMethod();
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -284,8 +298,13 @@ protected virtual bool UseEntityVariable
private static readonly MethodInfo SnapshotCollectionMethod
= typeof(SnapshotFactoryFactory).GetTypeInfo().GetDeclaredMethod(nameof(SnapshotCollection))!;
- [UsedImplicitly]
- private static HashSet? SnapshotCollection(IEnumerable? collection)
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static HashSet? SnapshotCollection(IEnumerable? collection)
=> collection == null
? null
: new HashSet(collection, ReferenceEqualityComparer.Instance);
diff --git a/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentProviderValueComparer.cs b/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentProviderValueComparer.cs
index 9a26cd70acf..a1448a4b41c 100644
--- a/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentProviderValueComparer.cs
+++ b/src/EFCore/ChangeTracking/Internal/StructuralEntryCurrentProviderValueComparer.cs
@@ -19,12 +19,10 @@ public class StructuralEntryCurrentProviderValueComparer : StructuralEntryCurren
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public StructuralEntryCurrentProviderValueComparer(
- IProperty property,
- ValueConverter converter)
+ public StructuralEntryCurrentProviderValueComparer(IProperty property)
: base(property)
{
- _converter = converter;
+ _converter = property.GetTypeMapping().Converter!;
}
///
diff --git a/src/EFCore/ChangeTracking/ValueComparer.cs b/src/EFCore/ChangeTracking/ValueComparer.cs
index 4226f3ecad1..2b5e80fd71f 100644
--- a/src/EFCore/ChangeTracking/ValueComparer.cs
+++ b/src/EFCore/ChangeTracking/ValueComparer.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
+using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.EntityFrameworkCore.ChangeTracking;
@@ -40,6 +41,24 @@ internal static readonly MethodInfo EqualityComparerEqualsMethod
internal static readonly MethodInfo ObjectGetHashCodeMethod
= typeof(object).GetRuntimeMethod(nameof(object.GetHashCode), Type.EmptyTypes)!;
+ private static readonly ConcurrentDictionary _genericSnapshotMethodMap = new();
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public static MethodInfo GetGenericSnapshotMethod(Type type)
+ => _genericSnapshotMethodMap.GetOrAdd(type, t =>
+ typeof(ValueComparer<>).MakeGenericType(t).GetGenericMethod(
+ nameof(ValueComparer.Snapshot),
+ genericParameterCount: 0,
+ BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly,
+ (a, b) => new[] { a[0] },
+ @override: false)!);
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore/ChangeTracking/ValueComparer`.cs b/src/EFCore/ChangeTracking/ValueComparer`.cs
index 5b7afaa4a82..84f7632f435 100644
--- a/src/EFCore/ChangeTracking/ValueComparer`.cs
+++ b/src/EFCore/ChangeTracking/ValueComparer`.cs
@@ -136,7 +136,7 @@ public ValueComparer(
if (typedEquals != null)
{
return Expression.Lambda>(
- type.IsClass
+ type.IsNullableType()
? Expression.OrElse(
Expression.AndAlso(
Expression.Equal(param1, Expression.Constant(null, type)),
diff --git a/src/EFCore/Design/ICSharpHelper.cs b/src/EFCore/Design/ICSharpHelper.cs
index e594a3ec20b..a42aa25949f 100644
--- a/src/EFCore/Design/ICSharpHelper.cs
+++ b/src/EFCore/Design/ICSharpHelper.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Numerics;
+using Microsoft.EntityFrameworkCore.Design.Internal;
namespace Microsoft.EntityFrameworkCore.Design;
@@ -319,7 +320,7 @@ string Literal(Dictionary values, bool vertical = fa
string Fragment(AttributeCodeFragment fragment);
///
- /// Generates a comma-sepearated argument list of values.
+ /// Generates a comma-separated argument list of values.
///
/// The values.
/// The argument list.
@@ -336,6 +337,8 @@ string Literal(Dictionary values, bool vertical = fa
/// Translates a node representing a statement into source code that would produce it.
///
/// The node to be translated.
+ /// Collection of translations for statically known instances.
+ /// Collection of translations for non-public member accesses.
/// Any namespaces required by the translated code will be added to this set.
/// Source code that would produce .
///
@@ -345,12 +348,17 @@ string Literal(Dictionary values, bool vertical = fa
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
[EntityFrameworkInternal]
- string Statement(Expression node, ISet collectedNamespaces);
+ string Statement(Expression node,
+ Dictionary? constantReplacements,
+ Dictionary? memberAccessReplacements,
+ ISet collectedNamespaces);
///
/// Translates a node representing an expression into source code that would produce it.
///
/// The node to be translated.
+ /// Collection of translations for statically known instances.
+ /// Collection of translations for non-public member accesses.
/// Any namespaces required by the translated code will be added to this set.
/// Source code that would produce .
///
@@ -360,5 +368,8 @@ string Literal(Dictionary values, bool vertical = fa
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
[EntityFrameworkInternal]
- string Expression(Expression node, ISet collectedNamespaces);
+ string Expression(Expression node,
+ Dictionary? constantReplacements,
+ Dictionary? memberAccessReplacements,
+ ISet collectedNamespaces);
}
diff --git a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs
index 75700524a3b..666a8cfe1bb 100644
--- a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs
+++ b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs
@@ -371,9 +371,11 @@ public static void Create(
.Append(codeHelper.Reference(converter.ProviderClrType))
.AppendLine(">(")
.IncrementIndent()
- .Append(codeHelper.Expression(converter.ConvertToProviderExpression, parameters.Namespaces))
+ .AppendLines(codeHelper.Expression(converter.ConvertToProviderExpression, null, null, parameters.Namespaces),
+ skipFinalNewline: true)
.AppendLine(",")
- .Append(codeHelper.Expression(converter.ConvertFromProviderExpression, parameters.Namespaces));
+ .AppendLines(codeHelper.Expression(converter.ConvertFromProviderExpression, null, null, parameters.Namespaces),
+ skipFinalNewline: true);
if (converter.ConvertsNulls)
{
@@ -428,11 +430,14 @@ public static void Create(
.Append(codeHelper.Reference(comparer.Type))
.AppendLine(">(")
.IncrementIndent()
- .AppendLines(codeHelper.Expression(comparer.EqualsExpression, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLines(codeHelper.Expression(comparer.EqualsExpression, null, null, parameters.Namespaces),
+ skipFinalNewline: true)
.AppendLine(",")
- .AppendLines(codeHelper.Expression(comparer.HashCodeExpression, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLines(codeHelper.Expression(comparer.HashCodeExpression, null, null, parameters.Namespaces),
+ skipFinalNewline: true)
.AppendLine(",")
- .AppendLines(codeHelper.Expression(comparer.SnapshotExpression, parameters.Namespaces), skipFinalNewline: true)
+ .AppendLines(codeHelper.Expression(comparer.SnapshotExpression, null, null, parameters.Namespaces),
+ skipFinalNewline: true)
.Append(")")
.DecrementIndent();
}
diff --git a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs
index 3cbce4374ec..d16cf567d77 100644
--- a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs
+++ b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs
@@ -30,6 +30,7 @@ public CSharpRuntimeAnnotationCodeGeneratorParameters(
IndentedStringBuilder methodBuilder,
ISet namespaces,
ISet scopeVariables,
+ Dictionary configurationClassNames,
bool nullable)
{
TargetName = targetName;
@@ -38,6 +39,7 @@ public CSharpRuntimeAnnotationCodeGeneratorParameters(
MethodBuilder = methodBuilder;
Namespaces = namespaces;
ScopeVariables = scopeVariables;
+ ConfigurationClassNames = configurationClassNames;
UseNullableReferenceTypes = nullable;
}
@@ -76,6 +78,11 @@ public CSharpRuntimeAnnotationCodeGeneratorParameters(
///
public ISet ScopeVariables { get; init; }
+ ///
+ /// The configuration class names corresponding to the structural types.
+ ///
+ public IReadOnlyDictionary ConfigurationClassNames { get; init; }
+
///
/// Indicates whether the given annotations are runtime annotations.
///
diff --git a/src/EFCore/Metadata/Internal/NullableEnumClrPropertySetter.cs b/src/EFCore/Design/Internal/MemberAccess.cs
similarity index 69%
rename from src/EFCore/Metadata/Internal/NullableEnumClrPropertySetter.cs
rename to src/EFCore/Design/Internal/MemberAccess.cs
index a8251dbff60..7bf8836a34c 100644
--- a/src/EFCore/Metadata/Internal/NullableEnumClrPropertySetter.cs
+++ b/src/EFCore/Design/Internal/MemberAccess.cs
@@ -1,7 +1,9 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
+// ReSharper disable once CheckNamespace
+
+namespace Microsoft.EntityFrameworkCore.Design.Internal;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -9,21 +11,15 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
-public class NullableEnumClrPropertySetter : IClrPropertySetter
- where TEntity : class
+public readonly record struct MemberAccess(MemberInfo member, bool assignment)
{
- private readonly Action _setter;
-
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public NullableEnumClrPropertySetter(Action setter)
- {
- _setter = setter;
- }
+ public readonly MemberInfo Member = member;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -31,13 +27,5 @@ public NullableEnumClrPropertySetter(Action setter)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual void SetClrValue(object instance, object? value)
- {
- if (value != null)
- {
- value = (TNonNullableEnumValue)value;
- }
-
- _setter((TEntity)instance, (TValue)value!);
- }
+ public readonly bool Assignment = assignment;
}
diff --git a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs
index 05b23dedcb5..077bf18df50 100644
--- a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs
+++ b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs
@@ -38,9 +38,9 @@ public static Expression MakeHasSentinel(
Expression.Constant(null, currentValueExpression.Type))
: isNullableValueType
? Expression.Not(
- Expression.Call(
+ Expression.MakeMemberAccess(
currentValueExpression,
- currentValueExpression.Type.GetMethod("get_HasValue")!))
+ currentValueExpression.Type.GetProperty("HasValue")!))
: Expression.Constant(false);
}
@@ -62,9 +62,9 @@ public static Expression MakeHasSentinel(
Expression.ReferenceEqual(
currentValueExpression,
Expression.Constant(null, currentValueExpression.Type)))
- : Expression.Call(
+ : Expression.MakeMemberAccess(
currentValueExpression,
- currentValueExpression.Type.GetMethod("get_HasValue")!),
+ currentValueExpression.Type.GetProperty("HasValue")!),
equalsExpression);
}
@@ -238,20 +238,20 @@ static Expression GenerateEqualExpression(
EF.MakePropertyMethod(typeof(object)),
entityParameterExpression,
Expression.Constant(property.Name, typeof(string))),
- Expression.Call(
+ Expression.MakeIndex(
keyValuesConstantExpression,
- ValueBuffer.GetValueMethod,
- Expression.Constant(i)))
+ ValueBuffer.Indexer,
+ new[] { Expression.Constant(i) }))
: Expression.Equal(
Expression.Call(
EF.MakePropertyMethod(property.ClrType),
entityParameterExpression,
Expression.Constant(property.Name, typeof(string))),
Expression.Convert(
- Expression.Call(
+ Expression.MakeIndex(
keyValuesConstantExpression,
- ValueBuffer.GetValueMethod,
- Expression.Constant(i)),
+ ValueBuffer.Indexer,
+ new[] { Expression.Constant(i) }),
property.ClrType));
}
}
diff --git a/src/EFCore/Infrastructure/Internal/DbSetFinder.cs b/src/EFCore/Infrastructure/Internal/DbSetFinder.cs
index 3200c0e3366..bab939cc544 100644
--- a/src/EFCore/Infrastructure/Internal/DbSetFinder.cs
+++ b/src/EFCore/Infrastructure/Internal/DbSetFinder.cs
@@ -27,7 +27,7 @@ public virtual IReadOnlyList FindSets(Type contextType)
private static DbSetProperty[] FindSetsNonCached(Type contextType)
{
- var factory = new ClrPropertySetterFactory();
+ var factory = ClrPropertySetterFactory.Instance;
return contextType.GetRuntimeProperties()
.Where(
diff --git a/src/EFCore/Internal/ManyToManyLoaderFactory.cs b/src/EFCore/Internal/ManyToManyLoaderFactory.cs
index cb8db2ec394..123174f8b38 100644
--- a/src/EFCore/Internal/ManyToManyLoaderFactory.cs
+++ b/src/EFCore/Internal/ManyToManyLoaderFactory.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Internal;
@@ -16,6 +17,18 @@ public class ManyToManyLoaderFactory
private static readonly MethodInfo GenericCreate
= typeof(ManyToManyLoaderFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateManyToMany))!;
+ private ManyToManyLoaderFactory()
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static readonly ManyToManyLoaderFactory Instance = new();
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore/Metadata/IPropertyBase.cs b/src/EFCore/Metadata/IPropertyBase.cs
index a180fa60267..f16ee93717d 100644
--- a/src/EFCore/Metadata/IPropertyBase.cs
+++ b/src/EFCore/Metadata/IPropertyBase.cs
@@ -49,14 +49,9 @@ public interface IPropertyBase : IReadOnlyPropertyBase, IAnnotatable
///
/// The to use.
MemberInfo GetMemberInfo(bool forMaterialization, bool forSet)
- {
- if (this.TryGetMemberInfo(forMaterialization, forSet, out var memberInfo, out var errorMessage))
- {
- return memberInfo!;
- }
-
- throw new InvalidOperationException(errorMessage);
- }
+ => this.TryGetMemberInfo(forMaterialization, forSet, out var memberInfo, out var errorMessage)
+ ? memberInfo!
+ : throw new InvalidOperationException(errorMessage);
///
/// Gets the property index for this property.
diff --git a/src/EFCore/Metadata/Internal/ClrAccessorFactory.cs b/src/EFCore/Metadata/Internal/ClrAccessorFactory.cs
index 66bf1fa92de..3fd5b512484 100644
--- a/src/EFCore/Metadata/Internal/ClrAccessorFactory.cs
+++ b/src/EFCore/Metadata/Internal/ClrAccessorFactory.cs
@@ -15,7 +15,7 @@ public abstract class ClrAccessorFactory
where TAccessor : class
{
private static readonly MethodInfo GenericCreate
- = typeof(ClrAccessorFactory).GetTypeInfo().GetDeclaredMethods(nameof(CreateGeneric)).Single();
+ = typeof(ClrAccessorFactory).GetMethod(nameof(CreateGeneric), BindingFlags.Instance | BindingFlags.NonPublic)!;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -32,7 +32,7 @@ private static readonly MethodInfo GenericCreate
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual TAccessor Create(MemberInfo memberInfo)
- => Create(memberInfo, null);
+ => CreateBase(memberInfo);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -40,23 +40,16 @@ public virtual TAccessor Create(MemberInfo memberInfo)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- protected virtual TAccessor Create(MemberInfo memberInfo, IPropertyBase? propertyBase)
+ protected virtual TAccessor CreateBase(MemberInfo memberInfo)
{
- var boundMethod = propertyBase != null
- ? GenericCreate.MakeGenericMethod(
- propertyBase.DeclaringType.ContainingEntityType.ClrType,
- propertyBase.DeclaringType.ClrType,
- propertyBase.ClrType,
- propertyBase.ClrType.UnwrapNullableType())
- : GenericCreate.MakeGenericMethod(
- memberInfo.DeclaringType!,
- memberInfo.DeclaringType!,
- memberInfo.GetMemberType(),
- memberInfo.GetMemberType().UnwrapNullableType());
+ var boundMethod = GenericCreate.MakeGenericMethod(
+ memberInfo.DeclaringType!,
+ memberInfo.DeclaringType!,
+ memberInfo.GetMemberType());
try
{
- return (TAccessor)boundMethod.Invoke(this, [memberInfo, propertyBase])!;
+ return (TAccessor)boundMethod.Invoke(this, new object?[] { memberInfo, null })!;
}
catch (TargetInvocationException e) when (e.InnerException != null)
{
@@ -71,8 +64,40 @@ protected virtual TAccessor Create(MemberInfo memberInfo, IPropertyBase? propert
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- protected abstract TAccessor CreateGeneric(
+ protected virtual TAccessor CreateBase(IPropertyBase propertyBase)
+ {
+ var boundMethod = GenericCreate.MakeGenericMethod(
+ propertyBase.DeclaringType.ContainingEntityType.ClrType,
+ propertyBase.DeclaringType.ClrType,
+ propertyBase.ClrType);
+
+ try
+ {
+ return (TAccessor)boundMethod.Invoke(this, new object?[] { GetMemberInfo(propertyBase), propertyBase })!;
+ }
+ catch (TargetInvocationException e) when (e.InnerException != null)
+ {
+ ExceptionDispatchInfo.Capture(e.InnerException).Throw();
+ throw;
+ }
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected abstract TAccessor CreateGeneric(
MemberInfo memberInfo,
IPropertyBase? propertyBase)
where TEntity : class;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected abstract MemberInfo GetMemberInfo(IPropertyBase propertyBase);
}
diff --git a/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs b/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs
index ef4fc85b7b6..73dc02c80b0 100644
--- a/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs
+++ b/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Runtime.ExceptionServices;
using JetBrains.Annotations;
namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -19,20 +20,23 @@ private static readonly MethodInfo GenericCreate
private static readonly MethodInfo CreateAndSetMethod
= typeof(ClrCollectionAccessorFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateAndSet))!;
- private static readonly MethodInfo CreateMethod
- = typeof(ClrCollectionAccessorFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateCollection))!;
-
private static readonly MethodInfo CreateAndSetHashSetMethod
= typeof(ClrCollectionAccessorFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateAndSetHashSet))!;
- private static readonly MethodInfo CreateHashSetMethod
- = typeof(ClrCollectionAccessorFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateHashSet))!;
-
private static readonly MethodInfo CreateAndSetObservableHashSetMethod
= typeof(ClrCollectionAccessorFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateAndSetObservableHashSet))!;
- private static readonly MethodInfo CreateObservableHashSetMethod
- = typeof(ClrCollectionAccessorFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateObservableHashSet))!;
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static readonly ClrCollectionAccessorFactory Instance = new();
+
+ private ClrCollectionAccessorFactory()
+ {
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -41,28 +45,30 @@ private static readonly MethodInfo CreateObservableHashSetMethod
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual IClrCollectionAccessor? Create(INavigationBase navigation)
- => !navigation.IsCollection ? null : Create(navigation, navigation.TargetEntityType);
-
- private static IClrCollectionAccessor? Create(IPropertyBase navigation, IEntityType? targetType)
{
+ if (!navigation.IsCollection)
+ {
+ return null;
+ }
+
// ReSharper disable once SuspiciousTypeConversion.Global
if (navigation is IClrCollectionAccessor accessor)
{
return accessor;
}
+ var targetType = navigation.TargetEntityType;
if (targetType == null)
{
return null;
}
- var memberInfo = GetMostDerivedMemberInfo();
+ var memberInfo = GetMostDerivedMemberInfo(navigation);
var propertyType = navigation.IsIndexerProperty() || navigation.IsShadowProperty()
? navigation.ClrType
: memberInfo!.GetMemberType();
var elementType = propertyType.TryGetElementType(typeof(IEnumerable<>));
-
if (elementType == null)
{
throw new InvalidOperationException(
@@ -93,125 +99,161 @@ private static readonly MethodInfo CreateObservableHashSetMethod
{
throw invocationException.InnerException!;
}
+ }
+
+ [UsedImplicitly]
+ private static IClrCollectionAccessor CreateGeneric(INavigationBase navigation)
+ where TEntity : class
+ where TCollection : class, IEnumerable
+ where TElement : class
+ {
+ CreateExpressions(
+ navigation,
+ out var getCollection,
+ out var setCollection,
+ out var setCollectionForMaterialization,
+ out var createAndSetCollection,
+ out var createCollection);
- MemberInfo? GetMostDerivedMemberInfo()
+ return new ClrICollectionAccessor(
+ navigation.Name,
+ navigation.IsShadowProperty(),
+ getCollection?.Compile(),
+ setCollection?.Compile(),
+ setCollectionForMaterialization?.Compile(),
+ createAndSetCollection?.Compile(),
+ createCollection?.Compile());
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual void Create(
+ INavigationBase navigation,
+ out Type entityType,
+ out Type propertyType,
+ out Type elementType,
+ out Expression? getCollection,
+ out Expression? setCollection,
+ out Expression? setCollectionForMaterialization,
+ out Expression? createAndSetCollection,
+ out Expression? createCollection)
+ {
+ var memberInfo = GetMostDerivedMemberInfo(navigation);
+ entityType = memberInfo?.DeclaringType ?? navigation.DeclaringType.ClrType;
+ propertyType = navigation.IsIndexerProperty() || navigation.IsShadowProperty()
+ ? navigation.ClrType
+ : memberInfo!.GetMemberType();
+
+ elementType = propertyType.TryGetElementType(typeof(IEnumerable<>))!;
+
+ var boundMethod = GenericCreateExpressions.MakeGenericMethod(entityType, propertyType, elementType);
+
+ try
+ {
+ var parameters = new object?[] { navigation, null, null, null, null, null };
+ boundMethod.Invoke(this, parameters);
+ getCollection = (Expression)parameters[1]!;
+ setCollection = (Expression)parameters[2]!;
+ setCollectionForMaterialization = (Expression?)parameters[3];
+ createAndSetCollection = (Expression)parameters[4]!;
+ createCollection = (Expression?)parameters[5];
+ }
+ catch (TargetInvocationException e) when (e.InnerException != null)
{
- var propertyInfo = navigation.PropertyInfo;
- var fieldInfo = navigation.FieldInfo;
-
- return (fieldInfo == null
- ? propertyInfo
- : propertyInfo == null
- ? fieldInfo
- : fieldInfo.FieldType.IsAssignableFrom(propertyInfo.PropertyType)
- ? propertyInfo
- : fieldInfo);
+ ExceptionDispatchInfo.Capture(e.InnerException).Throw();
+ throw;
}
}
+ private static readonly MethodInfo GenericCreateExpressions
+ = typeof(ClrCollectionAccessorFactory).GetMethod(nameof(CreateExpressions), BindingFlags.Static | BindingFlags.NonPublic)!;
+
[UsedImplicitly]
- private static IClrCollectionAccessor CreateGeneric(INavigationBase navigation)
+ private static void CreateExpressions(
+ INavigationBase navigation,
+ out Expression>? getCollection,
+ out Expression>? setCollection,
+ out Expression>? setCollectionForMaterialization,
+ out Expression, TCollection>>? createAndSetCollection,
+ out Expression>? createCollection)
where TEntity : class
where TCollection : class, IEnumerable
where TElement : class
{
+ getCollection = null;
+ setCollection = null;
+ setCollectionForMaterialization = null;
+ createAndSetCollection = null;
+ createCollection = null;
+
var entityParameter = Expression.Parameter(typeof(TEntity), "entity");
var valueParameter = Expression.Parameter(typeof(TCollection), "collection");
- Func? getterDelegate = null;
- Action? setterDelegate = null;
- Action? setterDelegateForMaterialization = null;
- Func, TCollection>? createAndSetDelegate = null;
- Func? createDelegate = null;
-
if (!navigation.IsShadowProperty())
{
var memberInfoForRead = navigation.GetMemberInfo(forMaterialization: false, forSet: false);
navigation.TryGetMemberInfo(forMaterialization: false, forSet: true, out var memberInfoForWrite, out _);
navigation.TryGetMemberInfo(forMaterialization: true, forSet: true, out var memberInfoForMaterialization, out _);
-
var memberAccessForRead = (Expression)Expression.MakeMemberAccess(entityParameter, memberInfoForRead);
if (memberAccessForRead.Type != typeof(TCollection))
{
memberAccessForRead = Expression.Convert(memberAccessForRead, typeof(TCollection));
}
- getterDelegate = Expression.Lambda>(
+ getCollection = Expression.Lambda>(
memberAccessForRead,
- entityParameter).Compile();
+ entityParameter);
if (memberInfoForWrite != null)
{
- setterDelegate = CreateSetterDelegate(entityParameter, memberInfoForWrite, valueParameter);
+ setCollection = CreateSetterDelegate(entityParameter, memberInfoForWrite, valueParameter);
}
if (memberInfoForMaterialization != null)
{
- setterDelegateForMaterialization = CreateSetterDelegate(entityParameter, memberInfoForMaterialization, valueParameter);
+ setCollectionForMaterialization = CreateSetterDelegate(entityParameter, memberInfoForMaterialization, valueParameter);
}
}
- var concreteType = new CollectionTypeFactory().TryFindTypeToInstantiate(
+ var concreteType = CollectionTypeFactory.Instance.TryFindTypeToInstantiate(
typeof(TEntity),
typeof(TCollection),
navigation.DeclaringEntityType.Model[CoreAnnotationNames.FullChangeTrackingNotificationsRequired] != null);
-
if (concreteType != null)
{
var isHashSet = concreteType.IsGenericType && concreteType.GetGenericTypeDefinition() == typeof(HashSet<>);
- if (setterDelegate != null
- || setterDelegateForMaterialization != null)
+ if (setCollection != null
+ || setCollectionForMaterialization != null)
{
- if (isHashSet)
- {
- createAndSetDelegate = (Func, TCollection>)CreateAndSetHashSetMethod
- .MakeGenericMethod(typeof(TEntity), typeof(TCollection), typeof(TElement))
- .CreateDelegate(typeof(Func, TCollection>));
- }
- else if (IsObservableHashSet(concreteType))
- {
- createAndSetDelegate = (Func, TCollection>)CreateAndSetObservableHashSetMethod
+ var setterParameter = Expression.Parameter(typeof(Action), "setter");
+
+ var createAndSetCollectionMethod = isHashSet
+ ? CreateAndSetHashSetMethod
.MakeGenericMethod(typeof(TEntity), typeof(TCollection), typeof(TElement))
- .CreateDelegate(typeof(Func, TCollection>));
- }
- else
- {
- createAndSetDelegate = (Func, TCollection>)CreateAndSetMethod
- .MakeGenericMethod(typeof(TEntity), typeof(TCollection), concreteType)
- .CreateDelegate(typeof(Func, TCollection>));
- }
+ : IsObservableHashSet(concreteType)
+ ? CreateAndSetObservableHashSetMethod
+ .MakeGenericMethod(typeof(TEntity), typeof(TCollection), typeof(TElement))
+ : CreateAndSetMethod
+ .MakeGenericMethod(typeof(TEntity), typeof(TCollection), concreteType);
+
+ createAndSetCollection = Expression.Lambda, TCollection>>(
+ Expression.Call(createAndSetCollectionMethod, entityParameter, setterParameter),
+ entityParameter,
+ setterParameter);
}
- if (isHashSet)
- {
- createDelegate = (Func)CreateHashSetMethod
- .MakeGenericMethod(typeof(TCollection), typeof(TElement))
- .CreateDelegate(typeof(Func));
- }
- else if (IsObservableHashSet(concreteType))
- {
- createDelegate = (Func)CreateObservableHashSetMethod
- .MakeGenericMethod(typeof(TCollection), typeof(TElement))
- .CreateDelegate(typeof(Func));
- }
- else
- {
- createDelegate = (Func)CreateMethod
- .MakeGenericMethod(typeof(TCollection), concreteType)
- .CreateDelegate(typeof(Func));
- }
+ createCollection = isHashSet
+ ? (() => (TCollection)(ICollection)new HashSet(ReferenceEqualityComparer.Instance))
+ : IsObservableHashSet(concreteType)
+ ? (() => (TCollection)(ICollection)new ObservableHashSet(ReferenceEqualityComparer.Instance))
+ : Expression.Lambda>(Expression.New(concreteType));
}
- return new ClrICollectionAccessor(
- navigation.Name,
- navigation.IsShadowProperty(),
- getterDelegate,
- setterDelegate,
- setterDelegateForMaterialization,
- createAndSetDelegate,
- createDelegate);
-
- static Action CreateSetterDelegate(
+ static Expression> CreateSetterDelegate(
ParameterExpression parameterExpression,
MemberInfo memberInfo,
ParameterExpression valueParameter1)
@@ -223,14 +265,33 @@ static Action CreateSetterDelegate(
valueParameter1,
memberInfo.GetMemberType())),
parameterExpression,
- valueParameter1).Compile();
+ valueParameter1);
+ }
+
+ private static MemberInfo? GetMostDerivedMemberInfo(INavigationBase navigation)
+ {
+ var propertyInfo = navigation.PropertyInfo;
+ var fieldInfo = navigation.FieldInfo;
+
+ return fieldInfo == null
+ ? propertyInfo
+ : propertyInfo == null
+ ? fieldInfo
+ : fieldInfo.FieldType.IsAssignableFrom(propertyInfo.PropertyType)
+ ? propertyInfo
+ : fieldInfo;
}
private static bool IsObservableHashSet(Type type)
=> type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ObservableHashSet<>);
- [UsedImplicitly]
- private static TCollection CreateAndSet(
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static TCollection CreateAndSet(
TEntity entity,
Action setterDelegate)
where TEntity : class
@@ -242,14 +303,13 @@ private static TCollection CreateAndSet()
- where TCollection : class
- where TConcreteCollection : TCollection, new()
- => new TConcreteCollection();
-
- [UsedImplicitly]
- private static TCollection CreateAndSetHashSet(
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static TCollection CreateAndSetHashSet(
TEntity entity,
Action setterDelegate)
where TEntity : class
@@ -261,14 +321,13 @@ private static TCollection CreateAndSetHashSet(
return collection;
}
- [UsedImplicitly]
- private static TCollection CreateHashSet()
- where TCollection : class
- where TElement : class
- => (TCollection)(ICollection)new HashSet(ReferenceEqualityComparer.Instance);
-
- [UsedImplicitly]
- private static TCollection CreateAndSetObservableHashSet(
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static TCollection CreateAndSetObservableHashSet(
TEntity entity,
Action setterDelegate)
where TEntity : class
@@ -279,10 +338,4 @@ private static TCollection CreateAndSetObservableHashSet()
- where TCollection : class
- where TElement : class
- => (TCollection)(ICollection)new ObservableHashSet(ReferenceEqualityComparer.Instance);
}
diff --git a/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs b/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs
index e98b055ec5a..91a8bb560bf 100644
--- a/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs
+++ b/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Runtime.ExceptionServices;
using Microsoft.EntityFrameworkCore.Internal;
namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -13,6 +14,18 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
///
public class ClrPropertyGetterFactory : ClrAccessorFactory
{
+ private ClrPropertyGetterFactory()
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static readonly ClrPropertyGetterFactory Instance = new();
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -20,7 +33,7 @@ public class ClrPropertyGetterFactory : ClrAccessorFactory
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public override IClrPropertyGetter Create(IPropertyBase property)
- => property as IClrPropertyGetter ?? Create(property.GetMemberInfo(forMaterialization: false, forSet: false), property);
+ => property as IClrPropertyGetter ?? CreateBase(property);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -28,9 +41,72 @@ public override IClrPropertyGetter Create(IPropertyBase property)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- protected override IClrPropertyGetter CreateGeneric(
+ protected override IClrPropertyGetter CreateGeneric(
MemberInfo memberInfo,
IPropertyBase? propertyBase)
+ {
+ CreateExpressions(memberInfo, propertyBase,
+ out var getterExpression, out var hasSentinelExpression, out var structuralGetterExpression, out var hasStructuralSentinelExpression);
+ return new ClrPropertyGetter(
+ getterExpression.Compile(),
+ hasSentinelExpression.Compile(),
+ structuralGetterExpression.Compile(),
+ hasStructuralSentinelExpression.Compile());
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override MemberInfo GetMemberInfo(IPropertyBase propertyBase)
+ => propertyBase.GetMemberInfo(forMaterialization: false, forSet: false);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual void Create(
+ IPropertyBase propertyBase,
+ out Expression getterExpression,
+ out Expression hasSentinelExpression,
+ out Expression structuralGetterExpression,
+ out Expression hasStructuralSentinelExpression)
+ {
+ var boundMethod = GenericCreateExpressions.MakeGenericMethod(
+ propertyBase.DeclaringType.ContainingEntityType.ClrType,
+ propertyBase.DeclaringType.ClrType,
+ propertyBase.ClrType);
+
+ try
+ {
+ var parameters = new object?[] { GetMemberInfo(propertyBase), propertyBase, null, null, null, null };
+ boundMethod.Invoke(this, parameters);
+ getterExpression = (Expression)parameters[2]!;
+ hasSentinelExpression = (Expression)parameters[3]!;
+ structuralGetterExpression = (Expression)parameters[4]!;
+ hasStructuralSentinelExpression = (Expression)parameters[5]!;
+ }
+ catch (TargetInvocationException e) when (e.InnerException != null)
+ {
+ ExceptionDispatchInfo.Capture(e.InnerException).Throw();
+ throw;
+ }
+ }
+
+ private static readonly MethodInfo GenericCreateExpressions
+ = typeof(ClrPropertyGetterFactory).GetMethod(nameof(CreateExpressions), BindingFlags.Instance | BindingFlags.NonPublic)!;
+
+ private void CreateExpressions(
+ MemberInfo memberInfo,
+ IPropertyBase? propertyBase,
+ out Expression> getterExpression,
+ out Expression> hasSentinelExpression,
+ out Expression> structuralGetterExpression,
+ out Expression> hasStructuralSentinelExpression)
{
var entityClrType = propertyBase?.DeclaringType.ContainingEntityType.ClrType ?? typeof(TEntity);
var propertyDeclaringType = propertyBase?.DeclaringType.ClrType ?? typeof(TEntity);
@@ -46,17 +122,16 @@ protected override IClrPropertyGetter CreateGeneric(
- Expression.Lambda>(readExpression, entityParameter).Compile(),
- Expression.Lambda>(hasSentinelValueExpression, entityParameter).Compile(),
- Expression.Lambda>(structuralReadExpression, structuralParameter).Compile(),
- Expression.Lambda>(hasStructuralSentinelValueExpression, structuralParameter).Compile());
+ getterExpression = Expression.Lambda>(readExpression, entityParameter);
+ hasSentinelExpression = Expression.Lambda>(hasSentinelValueExpression, entityParameter);
+ structuralGetterExpression = Expression.Lambda>(structuralReadExpression, structuralParameter);
+ hasStructuralSentinelExpression = Expression.Lambda>(hasStructuralSentinelValueExpression, structuralParameter);
Expression CreateReadExpression(ParameterExpression parameter, bool fromContainingType)
{
if (memberInfo.DeclaringType!.IsAssignableFrom(propertyDeclaringType))
{
- return PropertyBase.CreateMemberAccess(propertyBase, parameter, memberInfo, fromContainingType);
+ return PropertyAccessorsFactory.CreateMemberAccess(propertyBase, parameter, memberInfo, fromContainingType);
}
// This path handles properties that exist only on proxy types and so only exist if the instance is a proxy
@@ -72,7 +147,7 @@ Expression CreateReadExpression(ParameterExpression parameter, bool fromContaini
Expression.Condition(
Expression.ReferenceEqual(converted, Expression.Constant(null)),
Expression.Default(memberInfo.GetMemberType()),
- PropertyBase.CreateMemberAccess(propertyBase, converted, memberInfo, fromContainingType))
+ PropertyAccessorsFactory.CreateMemberAccess(propertyBase, converted, memberInfo, fromContainingType))
});
}
diff --git a/src/EFCore/Metadata/Internal/ClrPropertyMaterializationSetterFactory.cs b/src/EFCore/Metadata/Internal/ClrPropertyMaterializationSetterFactory.cs
index 5885a4b6ae5..3f405d0bdcf 100644
--- a/src/EFCore/Metadata/Internal/ClrPropertyMaterializationSetterFactory.cs
+++ b/src/EFCore/Metadata/Internal/ClrPropertyMaterializationSetterFactory.cs
@@ -11,12 +11,24 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
///
public class ClrPropertyMaterializationSetterFactory : ClrPropertySetterFactory
{
+ private ClrPropertyMaterializationSetterFactory()
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static new readonly ClrPropertyMaterializationSetterFactory Instance = new();
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public override IClrPropertySetter Create(IPropertyBase property)
- => property as IClrPropertySetter ?? Create(property.GetMemberInfo(forMaterialization: true, forSet: true), property);
+ protected override MemberInfo GetMemberInfo(IPropertyBase propertyBase)
+ => propertyBase.GetMemberInfo(forMaterialization: true, forSet: true);
}
diff --git a/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs b/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs
index f562e9660cc..3993a064981 100644
--- a/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs
+++ b/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Runtime.ExceptionServices;
+
namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
///
@@ -11,6 +13,24 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
///
public class ClrPropertySetterFactory : ClrAccessorFactory
{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected ClrPropertySetterFactory()
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static readonly ClrPropertySetterFactory Instance = new();
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -18,7 +38,7 @@ public class ClrPropertySetterFactory : ClrAccessorFactory
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public override IClrPropertySetter Create(IPropertyBase property)
- => property as IClrPropertySetter ?? Create(property.GetMemberInfo(forMaterialization: false, forSet: true), property);
+ => property as IClrPropertySetter ?? CreateBase(property);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -26,23 +46,87 @@ public override IClrPropertySetter Create(IPropertyBase property)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- protected override IClrPropertySetter CreateGeneric(
+ protected override IClrPropertySetter CreateGeneric(
MemberInfo memberInfo,
IPropertyBase? propertyBase)
+ {
+ CreateExpression(memberInfo, propertyBase, out var setter);
+ return new ClrPropertySetter(setter.Compile());
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override MemberInfo GetMemberInfo(IPropertyBase propertyBase)
+ => propertyBase.GetMemberInfo(forMaterialization: false, forSet: true);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual void Create(
+ IPropertyBase propertyBase,
+ out Expression setterExpression)
+ {
+ var boundMethod = GenericCreateExpression.MakeGenericMethod(
+ propertyBase.DeclaringType.ContainingEntityType.ClrType,
+ propertyBase.ClrType);
+
+ try
+ {
+ var parameters = new object?[] { GetMemberInfo(propertyBase), propertyBase, null };
+ boundMethod.Invoke(this, parameters);
+ setterExpression = (Expression)parameters[2]!;
+ }
+ catch (TargetInvocationException e) when (e.InnerException != null)
+ {
+ ExceptionDispatchInfo.Capture(e.InnerException).Throw();
+ throw;
+ }
+ }
+
+ private static readonly MethodInfo GenericCreateExpression
+ = typeof(ClrPropertySetterFactory).GetMethod(nameof(CreateExpression), BindingFlags.Instance | BindingFlags.NonPublic)!;
+
+ private void CreateExpression(
+ MemberInfo memberInfo,
+ IPropertyBase? propertyBase,
+ out Expression> setter) where TEntity : class
{
var entityClrType = propertyBase?.DeclaringType.ContainingEntityType.ClrType ?? typeof(TEntity);
var entityParameter = Expression.Parameter(entityClrType, "entity");
var propertyDeclaringType = propertyBase?.DeclaringType.ClrType ?? typeof(TEntity);
var valueParameter = Expression.Parameter(typeof(TValue), "value");
var memberType = memberInfo.GetMemberType();
- var convertedParameter = memberType == typeof(TValue)
- ? (Expression)valueParameter
- : Expression.Convert(valueParameter, memberType);
+ var convertedParameter = (Expression)valueParameter;
+
+ var propertyType = propertyBase?.ClrType ?? memberType;
+ if (propertyType.IsNullableType())
+ {
+ var unwrappedType = propertyType.UnwrapNullableType();
+ if (unwrappedType.IsEnum)
+ {
+ convertedParameter = Expression.Condition(
+ Expression.Equal(convertedParameter, Expression.Constant(null, convertedParameter.Type)),
+ convertedParameter,
+ Expression.Convert(Expression.Convert(convertedParameter, unwrappedType), convertedParameter.Type));
+ }
+ }
+
+ if (memberType != convertedParameter.Type)
+ {
+ convertedParameter = Expression.Convert(convertedParameter, memberType);
+ }
Expression writeExpression;
if (memberInfo.DeclaringType!.IsAssignableFrom(propertyDeclaringType))
{
- writeExpression = CreateMemberAssignment(propertyBase, entityParameter);
+ writeExpression = CreateMemberAssignment(memberInfo, propertyBase, entityParameter, convertedParameter);
}
else
{
@@ -58,89 +142,86 @@ protected override IClrPropertySetter CreateGeneric>(
+ setter = Expression.Lambda>(
writeExpression,
entityParameter,
- valueParameter).Compile();
-
- var propertyType = propertyBase?.ClrType ?? memberInfo.GetMemberType();
+ valueParameter);
- return propertyType.IsNullableType()
- && propertyType.UnwrapNullableType().IsEnum
- ? new NullableEnumClrPropertySetter(setter)
- : new ClrPropertySetter(setter);
-
- Expression CreateMemberAssignment(IPropertyBase? property, Expression instanceParameter)
+ static Expression CreateMemberAssignment(
+ MemberInfo memberInfo,
+ IPropertyBase? propertyBase,
+ Expression instanceParameter,
+ Expression convertedParameter)
{
- if (property?.DeclaringType is IComplexType complexType)
+ if (propertyBase?.DeclaringType is not IComplexType complexType)
{
- // The idea here is to create something like this:
- //
- // $level1 = $entity.k__BackingField;
- // $level2 = $level1.k__BackingField;
- // $level3 = $level2.k__BackingField;
- // $level3.k__BackingField = $value;
- // $level2.k__BackingField = $level3;
- // $level1.k__BackingField = $level2;
- // $entity.k__BackingField = $level1
- //
- // That is, we create copies of value types, make the assignment, and then copy the value back.
-
- var chain = complexType.ComplexProperty.GetChainToComplexProperty().ToList();
- var previousLevel = instanceParameter;
-
- var variables = new List();
- var assignments = new List();
- var chainCount = chain.Count;
- for (var i = 1; i <= chainCount; i++)
- {
- var currentProperty = chain[chainCount - i];
- var complexMemberInfo = currentProperty.GetMemberInfo(forMaterialization: false, forSet: false);
- var complexPropertyType = complexMemberInfo.GetMemberType();
- var currentLevel = Expression.Variable(complexPropertyType, $"level{i}");
- variables.Add(currentLevel);
- assignments.Add(
- Expression.Assign(
- currentLevel, PropertyBase.CreateMemberAccess(
- currentProperty,
- previousLevel,
- complexMemberInfo,
- fromContainingType: true)));
- previousLevel = currentLevel;
- }
-
- var propertyMemberInfo = property.GetMemberInfo(forMaterialization: false, forSet: true);
- assignments.Add(Expression.MakeMemberAccess(previousLevel, propertyMemberInfo).Assign(convertedParameter));
+ return propertyBase?.IsIndexerProperty() == true
+ ? Expression.Assign(
+ Expression.MakeIndex(
+ instanceParameter, (PropertyInfo)memberInfo, new List { Expression.Constant(propertyBase.Name) }),
+ convertedParameter)
+ : Expression.MakeMemberAccess(instanceParameter, memberInfo).Assign(convertedParameter);
+ }
- for (var i = chainCount - 1; i >= 0; i--)
- {
- var currentProperty = chain[chainCount - 1 - i];
- var complexMemberInfo = currentProperty.GetMemberInfo(forMaterialization: false, forSet: true);
- if (complexMemberInfo.GetMemberType().IsValueType)
- {
- var memberExpression = (MemberExpression)PropertyBase.CreateMemberAccess(
+ // The idea here is to create something like this:
+ //
+ // $level1 = $entity.k__BackingField;
+ // $level2 = $level1.k__BackingField;
+ // $level3 = $level2.k__BackingField;
+ // $level3.k__BackingField = $value;
+ // $level2.k__BackingField = $level3;
+ // $level1.k__BackingField = $level2;
+ // $entity.k__BackingField = $level1;
+ //
+ // That is, we create copies of value types, make the assignment, and then copy the value back.
+
+ var chain = complexType.ComplexProperty.GetChainToComplexProperty();
+ var previousLevel = instanceParameter;
+
+ var variables = new List();
+ var assignments = new List();
+ var chainCount = chain.Count;
+ for (var i = chainCount; i >= 1; i--)
+ {
+ var currentProperty = chain[chainCount - i];
+ var complexMemberInfo = currentProperty.GetMemberInfo(forMaterialization: false, forSet: false);
+ var complexPropertyType = complexMemberInfo.GetMemberType();
+ var currentLevel = Expression.Variable(complexPropertyType, $"level{chainCount + 1 - i}");
+ variables.Add(currentLevel);
+ assignments.Add(
+ Expression.Assign(
+ currentLevel, PropertyAccessorsFactory.CreateMemberAccess(
currentProperty,
- i == 0 ? instanceParameter : variables[i - 1],
+ previousLevel,
complexMemberInfo,
- fromContainingType: true);
+ fromContainingType: true)));
+ previousLevel = currentLevel;
+ }
- assignments.Add(memberExpression.Assign(variables[i]));
- }
- }
+ var propertyMemberInfo = propertyBase.GetMemberInfo(forMaterialization: false, forSet: true);
+ assignments.Add(Expression.MakeMemberAccess(previousLevel, propertyMemberInfo).Assign(convertedParameter));
- return Expression.Block(variables, assignments);
+ for (var i = 0; i <= chainCount - 1; i++)
+ {
+ var currentProperty = chain[chainCount - 1 - i];
+ var complexMemberInfo = currentProperty.GetMemberInfo(forMaterialization: false, forSet: true);
+ if (complexMemberInfo.GetMemberType().IsValueType)
+ {
+ var memberExpression = (MemberExpression)PropertyAccessorsFactory.CreateMemberAccess(
+ currentProperty,
+ i == (chainCount - 1) ? instanceParameter : variables[chainCount - 2 - i],
+ complexMemberInfo,
+ fromContainingType: true);
+
+ assignments.Add(memberExpression.Assign(variables[chainCount - 1 - i]));
+ }
}
- return propertyBase?.IsIndexerProperty() == true
- ? Expression.Assign(
- Expression.MakeIndex(
- instanceParameter, (PropertyInfo)memberInfo, new List { Expression.Constant(propertyBase.Name) }),
- convertedParameter)
- : Expression.MakeMemberAccess(instanceParameter, memberInfo).Assign(convertedParameter);
+ return Expression.Block(variables, assignments);
}
}
}
diff --git a/src/EFCore/Metadata/Internal/CollectionTypeFactory.cs b/src/EFCore/Metadata/Internal/CollectionTypeFactory.cs
index 0ad282c0895..bfb5a5ab703 100644
--- a/src/EFCore/Metadata/Internal/CollectionTypeFactory.cs
+++ b/src/EFCore/Metadata/Internal/CollectionTypeFactory.cs
@@ -14,6 +14,24 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
///
public class CollectionTypeFactory
{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected CollectionTypeFactory()
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static readonly CollectionTypeFactory Instance = new();
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -37,7 +55,6 @@ public class CollectionTypeFactory
// Else, return null.
var elementType = collectionType.TryGetElementType(typeof(IEnumerable<>));
-
if (elementType == null)
{
return null;
diff --git a/src/EFCore/Metadata/Internal/ComplexPropertyExtensions.cs b/src/EFCore/Metadata/Internal/ComplexPropertyExtensions.cs
index 91bc2a30871..1a2c8de0ee2 100644
--- a/src/EFCore/Metadata/Internal/ComplexPropertyExtensions.cs
+++ b/src/EFCore/Metadata/Internal/ComplexPropertyExtensions.cs
@@ -17,16 +17,13 @@ public static class ComplexPropertyExtensions
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public static IEnumerable GetChainToComplexProperty(this IComplexProperty property)
+ public static IReadOnlyList GetChainToComplexProperty(this IComplexProperty property)
{
- yield return property;
+ var chain = property.DeclaringType is IComplexType complexType
+ ? (List)complexType.ComplexProperty.GetChainToComplexProperty()
+ : new();
+ chain.Add(property);
- if (property.DeclaringType is IComplexType complexType)
- {
- foreach (var nestedProperty in complexType.ComplexProperty.GetChainToComplexProperty())
- {
- yield return nestedProperty;
- }
- }
+ return chain;
}
}
diff --git a/src/EFCore/Metadata/Internal/Navigation.cs b/src/EFCore/Metadata/Internal/Navigation.cs
index 0ede23739aa..f70a2e34c86 100644
--- a/src/EFCore/Metadata/Internal/Navigation.cs
+++ b/src/EFCore/Metadata/Internal/Navigation.cs
@@ -305,11 +305,7 @@ public virtual IClrCollectionAccessor? CollectionAccessor
ref _collectionAccessor,
ref _collectionAccessorInitialized,
this,
- static navigation =>
- {
- navigation.EnsureReadOnly();
- return new ClrCollectionAccessorFactory().Create(navigation);
- });
+ static navigation => ClrCollectionAccessorFactory.Instance.Create(navigation));
///
/// Runs the conventions when an annotation was set or removed.
diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs
index 4c74584ce15..f3686ed120b 100644
--- a/src/EFCore/Metadata/Internal/Property.cs
+++ b/src/EFCore/Metadata/Internal/Property.cs
@@ -24,7 +24,6 @@ public class Property : PropertyBase, IMutableProperty, IConventionProperty, IPr
private object? _sentinel;
private ValueGenerated? _valueGenerated;
private CoreTypeMapping? _typeMapping;
- private IComparer? _currentValueComparer;
private ConfigurationSource? _typeConfigurationSource;
private ConfigurationSource? _isNullableConfigurationSource;
@@ -33,6 +32,9 @@ public class Property : PropertyBase, IMutableProperty, IConventionProperty, IPr
private ConfigurationSource? _valueGeneratedConfigurationSource;
private ConfigurationSource? _typeMappingConfigurationSource;
+ // Warning: Never access these fields directly as access needs to be thread-safe
+ private IComparer? _currentValueComparer;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -1093,14 +1095,23 @@ public virtual CoreTypeMapping? TypeMapping
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual IComparer CurrentValueComparer
+ public virtual IComparer GetCurrentValueComparer()
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _currentValueComparer, this, static property =>
{
property.EnsureReadOnly();
- return new CurrentValueComparerFactory().Create(property);
+ return CurrentValueComparerFactory.Instance.Create(property);
});
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual void SetCurrentValueComparer(IComparer comparer)
+ => _currentValueComparer = comparer;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -2036,16 +2047,6 @@ void IMutableProperty.SetProviderClrType(Type? providerClrType)
providerClrType,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- [DebuggerStepThrough]
- IComparer IProperty.GetCurrentValueComparer()
- => CurrentValueComparer;
-
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
index 324a63d96ed..617f7f9c520 100644
--- a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
+++ b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Runtime.ExceptionServices;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Internal;
@@ -15,6 +16,18 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
///
public class PropertyAccessorsFactory
{
+ private PropertyAccessorsFactory()
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static readonly PropertyAccessorsFactory Instance = new();
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -27,22 +40,80 @@ public virtual PropertyAccessors Create(IPropertyBase propertyBase)
.Invoke(null, [propertyBase])!;
private static readonly MethodInfo GenericCreate
- = typeof(PropertyAccessorsFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateGeneric))!;
+ = typeof(PropertyAccessorsFactory).GetMethod(nameof(CreateGeneric), BindingFlags.Static | BindingFlags.NonPublic)!;
[UsedImplicitly]
private static PropertyAccessors CreateGeneric(IPropertyBase propertyBase)
{
- var property = propertyBase as IProperty;
+ CreateExpressions(
+ propertyBase,
+ out var currentValueGetter,
+ out var preStoreGeneratedCurrentValueGetter,
+ out var originalValueGetter,
+ out var relationshipSnapshotGetter,
+ out var valueBufferGetter);
return new PropertyAccessors(
- CreateCurrentValueGetter(propertyBase, useStoreGeneratedValues: true),
- CreateCurrentValueGetter(propertyBase, useStoreGeneratedValues: false),
- property == null ? null : CreateOriginalValueGetter(property),
- CreateRelationshipSnapshotGetter(propertyBase),
- property == null ? null : CreateValueBufferGetter(property));
+ currentValueGetter.Compile(),
+ preStoreGeneratedCurrentValueGetter.Compile(),
+ originalValueGetter?.Compile(),
+ relationshipSnapshotGetter.Compile(),
+ valueBufferGetter?.Compile());
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual void Create(
+ IPropertyBase propertyBase,
+ out Expression currentValueGetter,
+ out Expression preStoreGeneratedCurrentValueGetter,
+ out Expression? originalValueGetter,
+ out Expression relationshipSnapshotGetter,
+ out Expression? valueBufferGetter)
+ {
+ var boundMethod = GenericCreateExpressions.MakeGenericMethod(propertyBase.ClrType);
+
+ try
+ {
+ var parameters = new object?[] { propertyBase, null, null, null, null, null };
+ boundMethod.Invoke(null, parameters);
+ currentValueGetter = (Expression)parameters[1]!;
+ preStoreGeneratedCurrentValueGetter = (Expression)parameters[2]!;
+ originalValueGetter = (Expression?)parameters[3];
+ relationshipSnapshotGetter = (Expression)parameters[4]!;
+ valueBufferGetter = (Expression?)parameters[5];
+ }
+ catch (TargetInvocationException e) when (e.InnerException != null)
+ {
+ ExceptionDispatchInfo.Capture(e.InnerException).Throw();
+ throw;
+ }
+ }
+
+ private static readonly MethodInfo GenericCreateExpressions
+ = typeof(PropertyAccessorsFactory).GetMethod(nameof(CreateExpressions), BindingFlags.Static | BindingFlags.NonPublic)!;
+
+ private static void CreateExpressions(
+ IPropertyBase propertyBase,
+ out Expression> currentValueGetter,
+ out Expression> preStoreGeneratedCurrentValueGetter,
+ out Expression>? originalValueGetter,
+ out Expression> relationshipSnapshotGetter,
+ out Expression>? valueBufferGetter)
+ {
+ var property = propertyBase as IProperty;
+ currentValueGetter = CreateCurrentValueGetter(propertyBase, useStoreGeneratedValues: true);
+ preStoreGeneratedCurrentValueGetter = CreateCurrentValueGetter(propertyBase, useStoreGeneratedValues: false);
+ originalValueGetter = property == null ? null : CreateOriginalValueGetter(property);
+ relationshipSnapshotGetter = CreateRelationshipSnapshotGetter(propertyBase);
+ valueBufferGetter = property == null ? null : CreateValueBufferGetter(property);
}
- private static Func CreateCurrentValueGetter(
+ private static Expression> CreateCurrentValueGetter(
IPropertyBase propertyBase,
bool useStoreGeneratedValues)
{
@@ -71,7 +142,7 @@ private static Func