Skip to content

Lazy properties static proxy #1709

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

fredericDelaporte
Copy link
Member

This is a follow up to #1451.

The new static proxy factory was delegating the lazy properties proxy implementation to the old dynamic proxies.

With this PR, it does implement its own field interceptor, with far less reflection.

The PR also obsoletes dynamic proxies.

@@ -196,6 +196,8 @@
name: LinqReadonlyTestsContext
- conversion: Ignore
name: MultiThreadRunner
- conversion: Ignore
name: PeVerifier
Copy link
Member Author

@fredericDelaporte fredericDelaporte May 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was generating async tests for asynchronously calling the PEVerify. There is no point in testing that. Included in this commit because I have added tests using PeVerifier.

@@ -1,8 +1,10 @@
using System;
using System.Collections;
using NHibernate.Proxy.DynamicProxy;

namespace NHibernate.Test.DynamicEntity
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dynamic entity tests are using NHibernate dynamic proxies as a general purpose proxy provider. But that is not the purpose of NHibernate to provide such a thing. Those tests will have to be rewritten with an independent proxy provider if they are to be kept after dropping dynamic proxies.

public static TypeInfo CreateProxyType(System.Type baseType)
{
// Avoid having a suffix ending with "Proxy", for disambiguation with INHibernateProxy proxies
var typeName = $"{baseType.Name}ProxyForFieldInterceptor";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous implementation was causing field interceptor proxies to be build with same type and assembly name than INHibernateProxy. Apparently this was not causing any issue, even when lazily loading an entity as a INHibernateProxy loading its target implementation as a field interceptor proxy due to the entity having lazy properties.
Still it seems to me cleaner to avoid the confusion.

// be restored by lazy load. The field interceptor proxy cannot do that: it only knows how to load
// lazy properties, and not the other ones. (Moreover, its session is not restored on persistence
// context deserialization, contrary to NHibernateProxy. This causes deserialized proxies to only be
// usable for accessing data already loaded before serialization.)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those limitations are the same than those of dynamic proxies serialization of a field interceptor proxy.

@@ -24,7 +23,6 @@ internal class NHibernateProxyBuilder
private static readonly PropertyInfo LazyInitializerIdentifierProperty = LazyInitializerType.GetProperty(nameof(ILazyInitializer.Identifier));
private static readonly MethodInfo LazyInitializerInitializeMethod = LazyInitializerType.GetMethod(nameof(ILazyInitializer.Initialize));
private static readonly MethodInfo LazyInitializerGetImplementationMethod = LazyInitializerType.GetMethod(nameof(ILazyInitializer.GetImplementation), System.Type.EmptyTypes);
private static readonly IProxyAssemblyBuilder ProxyAssemblyBuilder = new DefaultProxyAssemblyBuilder();
Copy link
Member Author

@fredericDelaporte fredericDelaporte May 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not seen any interest in keeping the NHibernate.Proxy.DynamicProxy.IProxyAssemblyBuilder pattern now that it was used only by non-injectable private members. So I have rewritten its features into ProxyBuilderHelper, and obsoleted it.

internal static void EnableDynamicAssemblySaving(bool enable, string saveAssemblyPath)
{
_saveAssembly = enable;
_saveAssemblyPath = saveAssemblyPath;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DefaultProxyAssemblyBuilder was enabling that with compilation directives, which was not very practical. Moreover, it was not handling the save path, which was further complicating using the feature with potentially more file system access right troubles.

var factory = new ProxyFactory();
var interceptor = new DefaultDynamicLazyFieldInterceptor();
return factory.CreateProxy(PersistentClass, interceptor, typeof(IFieldInterceptorAccessor));
var cacheEntry = new ProxyCacheEntry(IsClassProxy ? PersistentClass : typeof(object), System.Type.EmptyTypes);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsClassProxy ? PersistentClass : typeof(object) has to be reverted to PersistentClass: the field interception proxy does not handle being an interface proxy. (I will add a test, this is lacking.)

(Optionally, it may be attempted here to handle being an interface proxy. But the INHibernateProxy interface will have to be filtered out of Interfaces. And the support would only be partial, as there will be no way of handling interface methods, but only properties. So I do not think it should be done.)

@fredericDelaporte fredericDelaporte changed the title Lazy properties static proxy WIP: Lazy properties static proxy May 30, 2018
if (baseType.IsInterface)
{
parentType = typeof(object);
interfaces.Add(baseType);
Copy link
Member Author

@fredericDelaporte fredericDelaporte May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does indeed not make sense for a field interceptor. The baseType can only be an interface for entities mapped as abstract. The field interceptor proxy should never be built for such case, it is built only on entity load from database. And anyway, it can not work in such case, since it uses its base implementation for storing its state.

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented May 30, 2018

I have addressed the two previous concerns, but I now look into changing even more the lazy property proxy implementation, for having it delegating its state like then lazy entity proxy. It would give two benefits: handling serialization the same way, and supporting the proxy mapping option, which currently is ignored by lazy property proxies.

@fredericDelaporte fredericDelaporte force-pushed the LazyPropertiesStaticProxy branch from 26f0af4 to 632d8a7 Compare May 31, 2018 12:22
@fredericDelaporte fredericDelaporte changed the title WIP: Lazy properties static proxy Lazy properties static proxy May 31, 2018
@fredericDelaporte fredericDelaporte force-pushed the LazyPropertiesStaticProxy branch from 632d8a7 to 5695457 Compare May 31, 2018 12:40
@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented May 31, 2018

I now look into changing even more the lazy property proxy implementation, for having it delegating its state like then lazy entity proxy.

This would require too much changes. Delegating its state causes issues to field setters and getters (from IPropertyAccessor), which ends up setting fields of the proxy instead of fields of the implementation. The entity proxy does not have the issue, because it is unwrapped and not directly supplied to accessors. This is handled in the persistence context, which makes a distinction between lazy entity proxies and entities. But there is no such thing with lazy properties proxies, which are handled as entities by the persistence context. Changing that seems to me a bit too much for the scope of this PR.

So I have squashed the fixups, and I consider this PR as ready for reviewing.

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented May 31, 2018

Checking again the dynamic proxy factory case, its serialization was ensuring the proxy type was rebuilt, which my implementation currently does not. Switching this PR to WIP again.

Testing this is a bit inconvenient as it requires to stop the process for unloading dynamic assemblies before deserializing. I intend to add some Explicit test allowing to check this manually.

Update: my implementation is broken (it fails deserializing), but there was no serialization test of lazy property proxies to demonstrate it.
But the dynamic lazy property proxy is also broken, while it does not fail deserializing. Instead, it yields a proxy object with all of its properties (lazy or not) having their default values. This happens because the proxy stores its data in its base class and implements ISerializable without attempting to serialize the base class data. This is a regression of b26e622 (fix for NH-3058, NH v3.4), which has ceased delegating the state to a persitentClass instance for storing it into the proxy base class. This is also the cause of #1481.

@fredericDelaporte fredericDelaporte changed the title Lazy properties static proxy WIP: Lazy properties static proxy May 31, 2018
@fredericDelaporte fredericDelaporte force-pushed the LazyPropertiesStaticProxy branch from 5695457 to 262f316 Compare June 1, 2018 01:03
@fredericDelaporte fredericDelaporte changed the title WIP: Lazy properties static proxy Lazy properties static proxy Jun 1, 2018
@fredericDelaporte fredericDelaporte force-pushed the LazyPropertiesStaticProxy branch from 262f316 to 5d77746 Compare June 1, 2018 01:12
@fredericDelaporte
Copy link
Member Author

Commits redone, for having a correct serialization from the first one. Serialization tests added.

Now this PR should really be ready.

@fredericDelaporte
Copy link
Member Author

About serialization, there is one "non-user-bug" case still unsupported: if the user supplies a serialization surrogate handling the entity type, it will be ignored by the proxy serialization. We do not have access to surrogates from the GetObjectData method. The only way to overcome this would be to copy the state of the proxy (excepted uninitialized properties) into a raw entity class instance and then to add it into info. Afterward copy it back to the proxy on deserialization (excepted uninitialized properties again). I do not consider it is worth the trouble.

@fredericDelaporte
Copy link
Member Author

fredericDelaporte commented Jun 1, 2018

Before merging I intend to check #1245 test case provided in its Jira (NH-2873), in case the failure is in the way proxies are built. (But indeed I expect it to be already fixed, by NH-3058.)

(Done: no failure.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants