强制惰性实体加载真实实例

2024-03-02

我有一个惰性实体的代理,它是通过加载子实体在会话中创建的。对父实体的后续提取仅返回 NH 代理。我需要实际实例来检查类型(实体已加入子类)。我一定错过了一些东西,但我找不到办法做到这一点。 Session.Refresh(proxy) 似乎没有帮助,我尝试过的任何 HQL 风格也没有帮助。

有人可以帮忙吗?


在我看来,与其解决这个问题,不如重新考虑你的设计。您绝对确定在这种情况下不能使用多态性 - 要么直接让实体负责您尝试执行的操作,要么使用访问者模式。我几次遇到这个问题,并且总是决定改变设计 - 它导致了更清晰的代码。我建议您也这样做,除非您完全确定依赖类型是最好的解决方案。

问题

为了让示例至少与现实世界有一些相似之处,我们假设您有以下实体:

public abstract class Operation
{
    public virtual DateTime PerformedOn { get; set; }
    public virtual double Ammount { get; set; }
}

public class OutgoingTransfer : Operation
{
    public virtual string TargetAccount { get; set; }
}

public class AtmWithdrawal : Operation
{
    public virtual string AtmAddress { get; set; }
}

它自然是更大模型的一小部分。现在您面临一个问题:对于每种具体类型的操作,都有不同的显示方式:

private static void PrintOperation(Operation operation)
{
    Console.WriteLine("{0} - {1}", operation.PerformedOn,
                      operation.Ammount);
}

private static void PrintOperation(OutgoingTransfer operation)
{
    Console.WriteLine("{0}: {1}, target account: {2}",
                      operation.PerformedOn, operation.Ammount,
                      operation.TargetAccount);
}

private static void PrintOperation(AtmWithdrawal operation)
{
    Console.WriteLine("{0}: {1}, atm's address: {2}",
                      operation.PerformedOn, operation.Ammount,
                      operation.AtmAddress);
}

简单的重载方法将在简单的情况下工作:

var transfer = new OutgoingTransfer
               {
                   Ammount = -1000,
                   PerformedOn = DateTime.Now.Date,
                   TargetAccount = "123123123"
               };

var withdrawal = new AtmWithdrawal
                 {
                     Ammount = -1000,
                     PerformedOn = DateTime.Now.Date,
                     AtmAddress = "Some address"
                 };

// works as intended
PrintOperation(transfer);
PrintOperation(withdrawal);

不幸的是,重载方法是在编译时绑定的,因此一旦引入数组/列表/任何操作,只会调用通用(操作操作)重载。

Operation[] operations = { transfer, withdrawal };
foreach (var operation in operations)
{
    PrintOperation(operation);
}

这个问题有两种解决方案,但都有缺点。您可以在操作中引入抽象/虚拟方法来将信息打印到选定的流。但这会将 UI 问题混合到您的模型中,因此这对您来说是不可接受的(稍后我将向您展示如何改进此解决方案以满足您的期望)。

您还可以以以下形式创建大量 if:

if(operation is (ConcreteType))
   PrintOperation((ConcreteType)operation);

这个解决方案很丑陋并且容易出错。每次添加/更改/删除操作类型时,您都必须检查使用这些 hack 的每个地方并对其进行修改。如果您错过了一个地方,您可能只能捕获该运行时 - 对某些错误(例如缺少一种子类型)没有严格的编译时检查。

此外,一旦引入任何类型的代理,该解决方案就会失败。

代理如何工作

下面的代码是非常简单的代理(在这个实现中,它与装饰器模式相同 - 但这些模式通常并不相同。需要一些额外的代码来区分这两种模式)。

public class OperationProxy : Operation
{
    private readonly Operation m_innerOperation;

    public OperationProxy(Operation innerOperation)
    {
        if (innerOperation == null)
            throw new ArgumentNullException("innerOperation");
        m_innerOperation = innerOperation;
    }


    public override double Ammount
    {
        get { return m_innerOperation.Ammount; }
        set { m_innerOperation.Ammount = value; }
    }

    public override DateTime PerformedOn
    {
        get { return m_innerOperation.PerformedOn; }
        set { m_innerOperation.PerformedOn = value; }
    }
}

正如您所看到的 - 整个层次结构只有一个代理类。为什么?因为您应该以不依赖于具体类型的方式编写代码 - 仅依赖于提供的抽象。该代理可以及时推迟实体加载 - 也许您根本不会使用它?也许您只会使用 1000 个实体中的 2 个?那为什么要加载它们呢?

因此 NHibernate 使用像上面这样的代理(不过更复杂)来推迟实体加载。它可以为每个子类型创建 1 个代理,但它会破坏延迟加载的整个目的。如果您仔细观察 NHibernate 如何存储子类,您会发现,为了确定实体是什么类型,您必须加载它。所以不可能有具体的代理——你只能有最抽象的,OperationProxy。

尽管 ifs 的解决方案很丑陋 - 但它是一个解决方案。现在,当您为问题引入代理时,它不再起作用。因此,我们只能使用多态方法,这是不可接受的,因为将 UI 责任混合到模型中。让我们解决这个问题。

依赖倒置和访问者模式

首先,让我们看看使用虚拟方法的解决方案是什么样子的(仅添加了代码):

public abstract class Operation
{
    public abstract void PrintInformation();
}

public class OutgoingTransfer : Operation
{
    public override void PrintInformation()
    {
        Console.WriteLine("{0}: {1}, target account: {2}",
                      PerformedOn, Ammount, TargetAccount);
    }
}

public class AtmWithdrawal : Operation
{
    public override void PrintInformation()
    {
        Console.WriteLine("{0}: {1}, atm's address: {2}",
                          PerformedOn, Ammount, AtmAddress);
    }
}

public class OperationProxy : Operation
{
    public override void PrintInformation()
    {
        m_innerOperation.PrintInformation();
    }
}

现在,当你打电话时:

Operation[] operations = { transfer, withdrawal, proxy };
foreach (var operation in operations)
{
    operation.PrintInformation();
}

一切都充满魅力。

为了消除模型中的 UI 依赖,让我们创建一个界面:

public interface IOperationVisitor
{
    void Visit(AtmWithdrawal operation);
    void Visit(OutgoingTransfer operation);
}

让我们修改模型以依赖于这个接口:

现在创建一个实现 - ConsoleOutputOperationVisitor (我已删除 PrintInformation 方法):

public abstract class Operation
{
    public abstract void Accept(IOperationVisitor visitor);
}

public class OutgoingTransfer : Operation
{
    public override void Accept(IOperationVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class AtmWithdrawal : Operation
{
    public override void Accept(IOperationVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class OperationProxy : Operation
{
    public override void Accept(IOperationVisitor visitor)
    {
        m_innerOperation.Accept(visitor);
    }
}

这里会发生什么?当您在操作上调用 Accept 并传递访问者时,将调用 Accept 的实现,其中将调用 Visit 方法的适当重载(编译器可以确定“this”的类型)。因此,您可以结合虚拟方法和重载的“力量”来调用适当的方法。正如您所看到的 - 现在 UI 参考在这里,模型仅依赖于一个接口,该接口可以包含在模型层中。

现在,为了使其正常工作,需要实现该接口:

 public class ConsoleOutputOperationVisitor : IOperationVisitor
 {
    #region IOperationVisitor Members
    public void Visit(AtmWithdrawal operation)
    {
        Console.WriteLine("{0}: {1}, atm's address: {2}",
                          operation.PerformedOn, operation.Ammount,
                          operation.AtmAddress);
    }

    public void Visit(OutgoingTransfer operation)
    {
        Console.WriteLine("{0}: {1}, target account: {2}",
                          operation.PerformedOn, operation.Ammount,
                          operation.TargetAccount);
    }

    #endregion
}

和代码:

Operation[] operations = { transfer, withdrawal, proxy };
foreach (var operation in operations)
{
    operation.Accept(visitor);
}

我很清楚这不是一个完美的解决方案。添加新类型时,您仍然需要修改界面和访问者。但是您可以进行编译时检查,并且永远不会错过任何内容。使用此方法确实很难实现的一件事是获得可插入的子类型 - 但无论如何我不相信这是一个有效的场景。您还必须修改此模式以满足您在具体场景中的需求,但我会将其留给您。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

强制惰性实体加载真实实例 的相关文章

随机推荐

  • 查找包中的所有函数(包括私有函数)

    I know ls package grid and find funs package grid in mvbutils但显然他们都找不到只能在内部或通过以下方式访问的非导出函数和方法 or getAnywhere 我必须在以下位置获取文
  • 我可以在 VS 中构建这个项目,但不能使用 msbuild

    我正在尝试在 nuget 应用程序中构建一个 ms 测试项目 我的项目结构是这样的 MyPackage gt MyPackage gt MyPackageTest 如果我打开 VS 并右键单击并构建 MyPackageTest 它可以工作
  • 找不到与给定名称“@style/Theme.Holo.Light.DarkActionBar”匹配的资源

    平台 4 3 API级别 18 AndroidManifest xml
  • 单击 IE 中图像上方的 div

    我有一个图像 上面可能有一些 div 指定该图像中的某些选择 这些 div 应该是可点击的 像这样的东西 divOuter width 500px height 500px border 2px solid 0000FF position
  • Cheerio / jquery 选择器:如何获取标签 a 中的文本?

    我正在尝试访问网站上的链接 该网站看起来像第一个代码示例 链接位于不同的 div 容器中 div div class class1 div class item class1 a href http www example com 1 ex
  • Spring Boot 的外部库文件夹

    我想知道如何为我的 Spring Boot 应用程序外部化所有 jdbc 驱动程序 我不想在构建应用程序后将 jdbc 驱动程序插入到我的 fat jar 中 有没有办法设置 java vm 参数 通知 jar 执行应包含哪个外部文件夹 或
  • 在 MSBuild 参数中设置应用程序名称

    我正在使用这个从命令行部署我的网络应用程序 msbuild WebApplication1 csproj t Package p configuration release 它工作正常 但部署的应用程序与项目设置页面中使用的名称相同 我想使
  • 在 R 中创建滚动列表

    给定一个向量 数据框的列 我想创建一个滚动向量 l 0 10 将返回 窗口为 3 0 1 2 1 2 3 2 3 4 3 4 5 1 滚动应用 r是一个 9x3 矩阵 其每一行都是所要求的列表元素之一 并且split将其转换为向量列表 尽管
  • Godot:调用外部方法

    经过大量谷歌搜索 我仍然不明白什么可能是一个简单的解决方案 场景 主要 包含一个 TileMap Grid 并附有一个脚本 Grid gd 场景 玩家 包含一个 KinematicBody2D Player 及其附加脚本 Player gd
  • 将嵌入资源保存到文件系统

    我使用此代码加载嵌入资源 位图图像 HRSRC hResInfo FindResource hInstance MAKEINTRESOURCE resourceId RT BITMAP HGLOBAL hRes LoadResource h
  • 在php联系表单中捕获用户的IP地址

    我正在尝试从 php 联系表单获取用户 IP 地址 我有以下代码 但我想知道以这种方式使用 clean string 向自己发送 IP 地址是否有效
  • Django:禁止(未设置 CSRF cookie。)

    我遇到了 CSRF cookie 未设置 的问题 我所需要的只是外部计费平台将更新发送到 django 服务器 在本地它可以与 Postman 一起使用 但在演示服务器中它不起作用 Code views py from django vie
  • 更改应用程序和任务栏图标 - Python/Tkinter

    我一直在使用 Tkinter 编写一个非常简单的 Python 脚本 我正在使用Python 2 7 3 如何更改其应用程序图标 资源管理器窗口中显示的 文件 图标和开始 所有程序例如 窗口 不是 文件类型 图标 也不是应用程序图标的主窗口
  • VirtualTreeView 的 Firemonkey 版本

    有谁知道是否有流行的 Firemonkey 版本在准备 另外 是否有人收集了一些将自定义控件移植到 Firemonkey 的经验 并且可以估计将虚拟树视图移植到 Firemonkey 需要多少工作 我们需要这个控件 并且只有当我们能让这个控
  • log4j 打印错误的字符

    有人报告我给他的使用 log4j 的程序无法正确打印字符 他告诉我 在文件中打印为 例如 Vid o 变成 Vid o 这可能是一些编码问题 但我喜欢重现问题以证明它已修复 我无法找到有关该主题的良好 且简短 文档 因此 是什么导致了这个问
  • 首次登录后 React 不重定向。但在强制重定向并再次执行登录操作后重定向

    我有一个登录表单 单击后会调用以下函数 总结一下 从 api 获取响应 如果有效 则设置 cookie 然后使用this props history push handleSubmit event event preventDefault
  • Apache 或其他一些 CLIENT JAVA 实现是否支持 HTTP/2?

    我正在寻找可以连接到基于 HTTP 2 的服务器的 java 客户端 该服务器已经支持 HTTP 2 API 我没有看到最流行的 Apache Http 客户端https hc apache org https hc apache org
  • 适用于移动设备的 Javascript onClick

    我正在开发一个导航子菜单 我需要可以通过移动和平板电脑设备访问它 我知道使用 onClick return true 可以解决问题 但是 我还需要在用户单击列表项时关闭列表项 基本上我需要它来切换子菜单 如果我添加这一行简单的 Javasc
  • 在 Kotlin 中使用线程还是协程更好?

    我正在从应用程序发送邮件 由于邮件发送需要时间并阻塞主线程 因此我正在创建一个新线程并将邮件发送任务移交给新线程 对于发送邮件的大量并发请求 我必须创建大量线程 但创建线程似乎很慢 我的问题是 如果我使用 Kotlin 的协程 它是否能提供
  • 强制惰性实体加载真实实例

    我有一个惰性实体的代理 它是通过加载子实体在会话中创建的 对父实体的后续提取仅返回 NH 代理 我需要实际实例来检查类型 实体已加入子类 我一定错过了一些东西 但我找不到办法做到这一点 Session Refresh proxy 似乎没有帮