4

我目前正在将 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 != nullPost但是,它目前失败了,因为我可以很容易地通过从它派生并使用受保护的构造函数来创建一个实例。在那种情况下_blog将是null.
我正在尝试以不变量确实受到保护的方式更改我的代码库。

NHibernate 乍一看需要受保护的构造函数来创建新实例,但是有一种方法可以绕过这个要求
该方法基本上使用FormatterServices.GetUninitializedObject. 重要的一点是,此方法不运行任何构造函数。
我可以使用这种方法,它可以让我摆脱受保护的构造函数。CodeContracts 的静态检查器现在会很高兴并且不再报告任何违规行为,但是一旦 NHibernate 尝试对此类实体进行水合,它将生成“不变失败”异常,因为它会尝试一个接一个地设置一个属性,并且每个属性设置器都会执行验证不变量的代码。

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

但是我该怎么做呢?

4

2 回答 2

2

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

除此之外,为什么您需要 100% 确定?这是某种方式的要求,还是你只是想覆盖所有的基础?

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

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

我想底线是,如果有人想弄乱您的域和课程,无论您做什么,他们都会这样做。在大多数情况下,阻止所有这些事情的努力并没有得到回报。

澄清后编辑

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

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

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

于 2013-02-27T12:17:12.943 回答
1

基于与 tucaz 的讨论,我在其核心相当简单的解决方案中提出了以下内容:

这个解决方案的核心是类NHibernateActivator。它有两个重要目的:

  1. 创建对象的实例而不调用其构造函数。它FormatterServices.GetUninitializedObject用于此。
  2. 在 NHibernate 水合实例时防止触发“不变失败”异常。这是一个两步任务:在 NHibernate 开始补水之前禁用不变检查,并在 NHibernate 完成后重新启用不变检查。
    第一部分可以在创建实例后直接执行。
    第二部分是使用接口IPostLoadEventListener

类本身非常简单:

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并且EnableInvariantEvaluation目前是使用反射来设置受保护字段的扩展方法。该字段防止检查不变量。此外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 NHibernateconfig.AppendListeners(ListenerType.PostLoad, ...);的监听器。ExposeConfiguration
  8. 我们的自定义字节码提供程序需要使用Environment.BytecodeProvider.
  9. 我们的自定义拦截器需要使用config.Interceptor = ...;.

当我有机会从所有这些中创建一个连贯的包并将其放在 github 上时,我将更新此答案。
此外,我想摆脱反射并创建一个代理类型,而不是直接访问受保护的 CodeContract 成员。

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


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

于 2013-02-28T09:39:37.227 回答