Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/EFCore.Proxies/Properties/ProxiesStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Proxies/Properties/ProxiesStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@
<data name="NonVirtualProperty" xml:space="preserve">
<value>Property '{1_entityType}.{0_property}' is not virtual. 'UseChangeTrackingProxies' requires all entity types to be public, unsealed, have virtual properties, and have a public or protected constructor. 'UseLazyLoadingProxies' requires only the navigation properties be virtual.</value>
</data>
<data name="NonVisibleProperty" xml:space="preserve">
<value>Property '{entityType}.{property}' cannot be overridden by a proxy because its {accessor} is not visible to the proxy assembly. Make it public or protected, or add '[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]' to the assembly that declares the entity type (a strong-named assembly must specify the proxy assembly's public key).</value>
</data>
<data name="ProxiesNotEnabled" xml:space="preserve">
<value>Unable to create proxy for '{entityType}' because proxies are not enabled. Call 'DbContextOptionsBuilder.UseChangeTrackingProxies' or 'DbContextOptionsBuilder.UseLazyLoadingProxies' to enable proxies.</value>
</data>
Expand Down
26 changes: 26 additions & 0 deletions src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs
Original file line number Diff line number Diff line change
@@ -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 Castle.DynamicProxy;
using Microsoft.EntityFrameworkCore.Internal;

namespace Microsoft.EntityFrameworkCore.Proxies.Internal;
Expand Down Expand Up @@ -92,6 +93,13 @@ public virtual void ProcessModelFinalizing(
throw new InvalidOperationException(
ProxiesStrings.NonVirtualProperty(navigationBase.Name, entityType.DisplayName()));
}

if (navigationBase.PropertyInfo.SetMethod is { } setMethod
&& !IsVisibleToProxies(setMethod))
{
throw new InvalidOperationException(
ProxiesStrings.NonVisibleProperty(navigationBase.Name, entityType.DisplayName(), "setter"));
}
}

if (_options.UseLazyLoadingProxies
Expand All @@ -113,6 +121,16 @@ public virtual void ProcessModelFinalizing(
ProxiesStrings.NonVirtualProperty(navigationBase.Name, entityType.DisplayName()));
}
}
else if (!IsVisibleToProxies(navigationBase.PropertyInfo.GetMethod!))
{
// The proxy cannot override the getter, so the navigation would silently never be loaded.
if (!_options.IgnoreNonVirtualNavigations
&& navigationBase is not INavigation { ForeignKey.IsOwnership: true })
{
throw new InvalidOperationException(
ProxiesStrings.NonVisibleProperty(navigationBase.Name, entityType.DisplayName(), "getter"));
}
}
else
{
navigationBase.SetPropertyAccessMode(PropertyAccessMode.Field);
Expand Down Expand Up @@ -205,4 +223,12 @@ public virtual void ProcessModelFinalizing(
}
}
}

// Defer to Castle so that the rules match exactly what the proxy generator can override, including
// [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]. Castle also requires the declaring type itself to be accessible,
// but an inaccessible entity type already fails with Castle's own error when a proxy is created, so only the visibility of
// the accessor is validated here.
private static bool IsVisibleToProxies(MethodInfo method)
=> !ProxyUtil.IsAccessible(method.DeclaringType!)
|| ProxyUtil.IsAccessible(method, out _);
}
Loading
Loading