ORM与CodeContracts结合使用的实体 - 确保不变量

时间:2013-02-27 11:22:02

标签: c# .net nhibernate code-contracts

我目前正在将CodeContracts添加到我现有的代码库中 证明困难的一件事是使用NHibernate水合的实体。

假设这个简单的类:

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);
    }
}

此类尝试保护不变_blog != null。但是,它目前失败了,因为我可以通过派生它并使用受保护的构造函数轻松地创建Post的实例。在这种情况下,_blog将为null 我试图以不变量确实受到保护的方式改变我的代码库。

NHibernate初次需要受保护的构造函数才能创建新实例,但有a way around this requirement
该方法基本上使用FormatterServices.GetUninitializedObject。重要的是,此方法不运行任何构造函数 我可以使用这种方法,它将允许我摆脱受保护的构造函数。 CodeContracts的静态检查器现在很高兴并且不再报告任何违规行为,但是一旦NHibernate试图给这些实体加水,就会生成“不变的失败”异常,因为它试图在另一个属性设置器执行后设置一个属性用于验证不变量的代码。

因此,为了完成所有这些工作,我必须确保通过公共构造函数实例化实体。

但我该怎么做?

2 个答案:

答案 0 :(得分:2)

丹尼尔,如果我没有弄错(自从我与NH合作已经有一段时间了)你可以拥有一个私人构造函数,他仍然可以很好地创建你的对象。

除此之外,为什么你需要100%肯定?这在某种程度上是一种要求还是你只是想覆盖所有的基础?

我问这是因为根据要求我们可以采用另一种方式实现它。

你现在可以提供的额外保护是连接IInterceptor类,以确保在加载后你的类仍然有效。

我想最重要的是,如果有人想要搞乱你的域名和课程,无论你做什么,他们都会这样做。在大多数情况下,防止所有这些东西的努力都没有得到回报。

澄清后编辑

如果您使用对象写入数据库并且合同正在运行,您可以安全地假设数据将被正确写入,因此如果没有人篡改数据库,则正确加载。

如果您手动更改数据库,则应该停止执行此操作并使用域来执行此操作(验证逻辑所在的位置)或测试数据库更改过程。

尽管如此,如果你真的需要,你仍然可以连接一个IInterceptor,它将在负载后验证你的实体,但我不认为你通过确保你的房子管道好了来解决来自街道的水淹。

答案 1 :(得分:1)

根据与tucaz的讨论,我想出了以下内容,其核心相当简单:

此解决方案的核心是班级NHibernateActivator。它有两个重要的目的:

  1. 在不调用其构造函数的情况下创建对象的实例。它使用FormatterServices.GetUninitializedObject
  2. 防止在NHibernate水合实例时触发“不变失败”异常。这是一个两步任务:在NHibernate开始保湿之前禁用不变检查,并在NHibernate完成后重新启用不变检查。
    第一部分可以在创建实例后直接执行 第二部分使用界面IPostLoadEventListener
  3. 课程本身很简单:

    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);
        }
    }
    

    DisableInvariantEvaluationEnableInvariantEvaluation是当前使用反射设置受保护字段的扩展方法。此字段可防止检查不变量。此外,EnableInvariantEvaluation将执行检查不变量是否通过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]);
        }
    }
    

    其余的是NHibernate管道:

    1. 我们需要实现一个使用我们的激活器的拦截器。
    2. 我们需要实现一个反射优化器,它返回IInstantiationOptimizer的实现。这个实现反过来再次使用我们的激活器。
    3. 我们需要实现一个使用我们的激活器的代理工厂。
    4. 我们需要实现IProxyFactoryFactory来返回我们的自定义代理工厂。
    5. 我们需要创建一个自定义代理验证器,它不关心该类型是否具有默认构造函数。
    6. 我们需要实现一个字节码提供程序,它返回我们的反射优化器和代理工厂工厂。
    7. NHibernateActivator需要在Fluent NHibernate的config.AppendListeners(ListenerType.PostLoad, ...);中使用ExposeConfiguration注册为监听器。
    8. 我们的自定义字节码提供程序需要使用Environment.BytecodeProvider注册。
    9. 我们的自定义拦截器需要使用config.Interceptor = ...;注册。
    10. 当我有机会从所有这些中创建一个连贯的包并将其放在github上时,我会更新这个答案。
      此外,我想摆脱反射并创建一个代理类型,而不是可以直接访问受保护的CodeContract成员。

      供参考,以下博客文章有助于实现几个NHibernate接口:


      不幸的是,对于具有复合键的实体,这当前是失败的,因为反射优化器不用于它们。这实际上是NHibernate中的一个错误,我报告了它here