Entities used by ORM in combination with CodeContracts - ensure invariants

扶醉桌前 提交于 2019-12-07 11:03:44

问题


I am currently in the process of adding CodeContracts to my existing code base.
One thing that proves difficult is the usage of entities that are hydrated by NHibernate.

Assume this simple class:

public class Post
{
    private Blog _blog;

    [Obsolete("Required by NHibernate")]
    protected Post() { }

    public Post(Blog blog)
    {
        Contract.Requires(blog != null);
        _blog = blog;
    }

    public Blog Blog
    {
        get
        {
            Contract.Ensures(Contract.Result<Blog>() != null);
            return _blog;
        }
        set
        {
            Contract.Requires(value != null);
            _blog = value;
        }
    }

    [ContractInvariantMethod]
    private void Invariants()
    {
        Contract.Invariant(_blog != null);
    }
}

This class tries to protect the invariant _blog != null. However, it currently fails, because I easily could create an instance of Post by deriving from it and using the protected constructor. In that case _blog would be null.
I am trying to change my code-base in a way that the invariants are indeed protected.

The protected constructor is at first sight needed by NHibernate to be able to create new instances, but there is a way around this requirement.
That approach basically uses FormatterServices.GetUninitializedObject. The important point is, that this method doesn't run any constructors.
I could use this approach and it would allow me to get rid of the protected constructor. The static checker of CodeContracts would now be happy and not report any more violations, but as soon as NHibernate tries to hydrate such entities it will generate "invariant failed" exceptions, because it tries to set one property after the other and every property setter executes code that verifies the invariants.

So, to make all this work, I will have to ensure that the entities are instantiated via their public constructor.

But how would I do this?


回答1:


Daniel, if I'm not mistaken (it's been a while since I worked with NH) you can have a private constructor and he still should be fine creating your object.

Aside from that, why do you need to be a 100% sure? Is it a requirement in some way or you are just trying to covering all the bases?

I ask that because depending on the requirement we could come with another way of achieving it.

What you COULD do right now to provide that extra protection is wire up an IInterceptor class to make sure that after the load your class is still valid.

I guess that the bottom line is if someone want's to mess up with your domain and classes they WILL do it no matter what you do. The effort to prevent all that stuff doesn't pay off in most cases.

Edit after clarification

If you use your objects to write to the database and you contracts are working you can safely assume that the data will be written correctly and therefore loaded correctly if no one tampers with the database.

If you do change the database manually you should either stop doing it and use your domain to do that (that's where the validation logic is) or test the database changing process.

Still, if you really need that you can still hook up a IInterceptor that will validate your entity after the load, but I don't think you fix a water flooding coming from the street by making sure your house pipe is fine.




回答2:


Based on the discussion with tucaz, I came up with the following, in its core rather simple solution:

The heart of this solution is the class NHibernateActivator. It has two important purposes:

  1. Create an instance of an object without invoking its constructors. It uses FormatterServices.GetUninitializedObject for this.
  2. Prevent the triggering of "invariant failed" exceptions while NHibernate hydrates the instance. This is a two-step task: Disable invariant checking before NHibernate starts hydrating and re-enable invariant checking after NHibernate is done.
    The first part can be performed directly after the instance has been created.
    The second part is using the interface IPostLoadEventListener.

The class itself is pretty simple:

public class NHibernateActivator : INHibernateActivator, IPostLoadEventListener
{
    public bool CanInstantiate(Type type)
    {
        return !type.IsAbstract && !type.IsInterface &&
               !type.IsGenericTypeDefinition && !type.IsSealed;
    }

    public object Instantiate(Type type)
    {
        var instance = FormatterServices.GetUninitializedObject(type);
        instance.DisableInvariantEvaluation();
        return instance;
    }

    public void OnPostLoad(PostLoadEvent @event)
    {
        if (@event != null && @event.Entity != null)
            @event.Entity.EnableInvariantEvaluation(true);
    }
}

DisableInvariantEvaluation and EnableInvariantEvaluation are currently extension methods that use reflection to set a protected field. This field prevents invariants from being checked. Furthermore EnableInvariantEvaluation will execute the method that checks the invariants if it gets passed true:

public static class CodeContractsExtensions
{
    public static void DisableInvariantEvaluation(this object entity)
    {
        var evaluatingInvariantField = entity.GetType()
                                             .GetField(
                                                 "$evaluatingInvariant$", 
                                                 BindingFlags.NonPublic | 
                                                 BindingFlags.Instance);
        if (evaluatingInvariantField == null)
            return;
        evaluatingInvariantField.SetValue(entity, true);
    }

    public static void EnableInvariantEvaluation(this object entity,
                                                 bool evaluateNow)
    {
        var evaluatingInvariantField = entity.GetType()
                                             .GetField(
                                                 "$evaluatingInvariant$", 
                                                 BindingFlags.NonPublic | 
                                                 BindingFlags.Instance);
        if (evaluatingInvariantField == null)
            return;
        evaluatingInvariantField.SetValue(entity, false);

        if (!evaluateNow)
            return;
        var invariantMethod = entity.GetType()
                                    .GetMethod("$InvariantMethod$",
                                               BindingFlags.NonPublic | 
                                               BindingFlags.Instance);
        if (invariantMethod == null)
            return;
        invariantMethod.Invoke(entity, new object[0]);
    }
}

The rest is NHibernate plumbing:

  1. We need to implement an interceptor that uses our activator.
  2. We need to implement an reflection optimizer that returns our implementation of IInstantiationOptimizer. This implementation in turn again uses our activator.
  3. We need to implement a proxy factory that uses our activator.
  4. We need to implement IProxyFactoryFactory to return our custom proxy factory.
  5. We need to create a custom proxy validator that doesn't care whether the type has a default constructor.
  6. We need to implement a bytecode provider that returns our reflection optimizer and proxy-factory factory.
  7. NHibernateActivator needs to be registered as a listener using config.AppendListeners(ListenerType.PostLoad, ...); in ExposeConfiguration of Fluent NHibernate.
  8. Our custom bytecode provider needs to be registered using Environment.BytecodeProvider.
  9. Our custom interceptor needs to be registered using config.Interceptor = ...;.

I will update this answer when I had the chance to create a coherent package out of all this and put it on github.
Furthermore, I want to get rid of the reflection and create a proxy type instead that can directly access the protected CodeContract members.

For reference, the following blog posts where helpful in implementing the several NHibernate interfaces:

  • http://weblogs.asp.net/ricardoperes/archive/2012/06/19/implementing-an-interceptor-using-nhibernate-s-built-in-dynamic-proxy-generator.aspx
  • http://kozmic.net/2011/03/20/working-with-nhibernate-without-default-constructors/

Unfortunately, this currently fails for entities with composite keys, because the reflection optimizer is not used for them. This is actually a bug in NHibernate and I reported it here.



来源:https://stackoverflow.com/questions/15110837/entities-used-by-orm-in-combination-with-codecontracts-ensure-invariants

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!