在我的设计框架业务中,字典大类、部门机构、系统菜单等这些表,都存在id、pid的字段,主要是作为自引用关系,实现树形列表数据的处理的,因为这样可以实现无限层级的树形列表。在实际使用Pydantic和SqlAlchemy来直接处理嵌套关系的时候,总是出现数据在Pydantic的对象转换验证上,爬坑一段时间才发现是模型定义使用上的问题,本篇随笔介绍使用Pydantic和SqlAlchemy实现树形列表数据(自引用表关系)的处理,以及递归方式处理数据差异。

1、使用Pydantic和SqlAlchemy实现树形列表数据(自引用表关系)的处理

默认的机构表的sqlalchemy的模型定义如下所示。

class Ou(Base):
"""机构(部门)信息-表模型""" __tablename__ = "t_acl_ou" id = Column(Integer, primary_key=True, comment="主键", autoincrement=True)
pid = Column(Integer, ForeignKey("t_acl_ou.id"), comment="父级机构ID")

****其他信息

# 定义 parent 关系
parent = relationship(
"Ou", remote_side=[id], back_populates="children") # 定义 children 关系
children = relationship("Ou", back_populates="parent")

然后对应的DTO(Schema)数据类定义如下。

class OuDto(BaseModel):
id: Optional[int] = None
pid: Optional[int] = None
***其他信息 class OuNodeDto(OuDto):
"""部门机构节点对象""" children: Optional[List["OuNodeDto"]] = None # 这里使用 Optional class Config:
orm_mode = True # 启用 orm_mod
from_attributes = True
extra = "allow"

然后我在机构的Crud类里面定义了一个get_children的函数,如下所示

    async def get_children(self, db: AsyncSession, id: int) -> Ou:
"""
获取子列表
:param db:
:param id:
:return:
"""
result = await db.execute(
select(Ou).options(selectinload(Ou.children)).where(Ou.id == id)
)
result = result.scalar_one_or_none()
return result

这里通过 selectinload 的加载方式,可以再数据检索的时候,同时获得子列表的处理。

为了验证数据能够再CRUD中正常的检索出来,我对CRUD类的接口进行测试,并查询获得其中的children集合,代码如下所示

async def test_list_ou():
async with async_session() as db:
ou = await ou_crud.get_children(db, "3")
print(vars(ou))
for o in ou.children:
print(vars(o)) await db.close()

其中机构id为3的,是广州分公司,它把该公司下属所有的机构都能正常读取出来,因此底层没有问题。

但是利用FastAPI的接口处理,通过pydantic的数据转换就是不能正常获得,如下是FastAPI的路由接口实现。

@router.get(
"/get-children",
response_model=AjaxResponse[OuNodeDto | None],
summary="根据名称获取客户",
dependencies=[DependsJwtAuth],
)
async def get_children(
id: Annotated[int | None, Query()] = None,
db: AsyncSession = Depends(get_db),
):
ou = await ou_crud.get_children(db, id)
try:
result = OuNodeDto.model_validate(ou)
except Exception as e:
print(e.json())
return AjaxResponse(
success=False, result=None, errorInfo=ErrorInfo(message=str(e))
)
return AjaxResponse(result)

这里注意,我使用 OuNodeDto.model_validate(ou) 对嵌套列表对象进行转换的,出错就是在这里。

具体我们可以再Swagger界面中调试获得错误信息。

我反复核对模型Model和Schema的对象都是一一对应的,错误不是字段名称的问题,因此可能是数据处理上的问题,但是很难从其中获得更多有用的信息。在Chatgpt中获得的错误提示如下所示。

为什么我的嵌套列表可以查询出来,而通过Schema的model_validate转换的时候,就提示validation errors?

你遇到的这个问题可能是因为 Pydantic 的模型验证过程中遇到了与嵌套数据结构相关的不一致性或问题。以下是一些常见的原因及其解决方法:

1)嵌套结构不匹配

确保你的嵌套数据结构与 Pydantic 模型定义一致。如果你的模型预期的是一个嵌套的列表,但数据库查询返回的结果中缺少某些字段或类型不匹配,可能会导致验证失败。

解决方法:

  • 检查从数据库查询出来的数据结构,确保它与 Pydantic 模型中的嵌套定义一致。

2)前向引用 (Forward References)

如果你的 Pydantic 模型有自引用或互相引用的情况,确保你正确使用了 update_forward_refs()。如果在嵌套引用中没有正确处理前向引用,可能会导致验证失败。

解决方法:

  确保你在 Pydantic 模型定义之后调用了 update_forward_refs(),pydantic新版使用model_rebuild,特别是当模型包含递归引用时。

3)使用from_attributes 选项

如果你的数据来自 ORM 对象(或任何非字典的对象),而你使用的是 Pydantic v2,可以尝试使用 from_attributes 属性。

解决方法:

  • Config 中启用 from_attributes 选项以确保 Pydantic 模型能够从属性中提取数据。

4) 数据库返回的数据类型问题

确保从数据库返回的数据类型(特别是 children 字段)是你预期的类型。如果 children 返回的不是一个列表或是一个包含子对象的对象,则会导致验证失败。

解决方法:

  检查你的查询逻辑,确保 children 字段返回的是一个列表,并且列表中的每个项符合 OuNodeDto模型的要求。

5)未正确处理关系加载

在使用 SQLAlchemy 时,确保相关数据(如 children)已被正确加载。如果尝试访问未加载的关系,可能会抛出 StatementError

result = await db.execute(
select(Ou).options(selectinload(Ou.children)).where(Ou.id == id)
)

最后发现这些我都已经做了,我的pydantic模型定义如下所示,还是会出错。

class OuNodeDto(OuDto):
"""部门机构节点对象""" children: Optional[List["OuNodeDto"]] = None # 这里使用 Optional class Config:
orm_mode = True # 启用 orm_mod
from_attributes = True
extra = "allow" # 更新前向引用
OuNodeDto.model_rebuild(force=True)

如果记录返回的对象是正常的,但在使用 OuNodeDto.model_validate(ou) 转换时出现错误,可能的问题出在 Pydantic 模型的定义或对象结构与 Pydantic 模型预期的格式不完全匹配。

最后发现是 relationshiplazy 参数的加载策略的影响

lazy 加载策略的概述

在 SQLAlchemy 中,relationshiplazy 参数决定了如何和何时加载相关的对象。常见的 lazy 加载策略有:

  1. lazy="immediate":

    • 定义:这种策略会在加载父对象时立即加载相关的子对象。即,父对象和它的子对象会在同一个查询中加载。
    • 为什么有效:因为 lazy="immediate" 会立即加载所有相关的对象,当你使用 Pydantic 的 model_validate 进行数据验证时,相关对象已经被加载并可供访问。
  2. lazy="select":

    • 定义:这种策略会在访问关系时通过单独的查询来加载相关对象。即,只有当你访问子对象时,SQLAlchemy 才会发起额外的查询来获取这些对象。
    • 为什么可能无效:由于子对象是在访问时才加载的,因此在你进行 model_validate 验证时,可能子对象还没有被加载,导致验证失败。
  3. lazy="dynamic":

    • 定义:这种策略返回一个查询对象而不是实际的子对象。你需要显式地执行这个查询来获取相关的子对象。
    • 为什么可能无效lazy="dynamic" 返回的是查询对象而不是实际的对象实例。因此,Pydantic 的 model_validate 无法直接处理这些查询对象,必须先执行查询来获取实际的对象。

使用 lazy="immediate"

class Ou(Base):
__tablename__ = "t_acl_ou"
id = Column(Integer, primary_key=True)
pid = Column(Integer, ForeignKey("t_acl_ou.id"))
name = Column(String)
parent = relationship("Ou", remote_side=[id], back_populates="children", lazy="immediate")
children = relationship("Ou", back_populates="parent", lazy="immediate")

在这种情况下,当你查询一个 Ou 对象时,children 已经被立即加载,可以直接用于 Pydantic 的 model_validate

使用 lazy="select"

class Ou(Base):
__tablename__ = "t_acl_ou"
id = Column(Integer, primary_key=True)
pid = Column(Integer, ForeignKey("t_acl_ou.id"))
name = Column(String)
parent = relationship("Ou", remote_side=[id], back_populates="children", lazy="select")
children = relationship("Ou", back_populates="parent", lazy="select")

在这种策略下,children 只有在访问时才会被加载,这可能导致在进行 model_validate 验证时 children 尚未加载完成。

使用 lazy="dynamic"

class Ou(Base):
__tablename__ = "t_acl_ou"
id = Column(Integer, primary_key=True)
pid = Column(Integer, ForeignKey("t_acl_ou.id"))
name = Column(String)
parent = relationship("Ou", remote_side=[id], back_populates="children", lazy="dynamic")
children = relationship("Ou", back_populates="parent", lazy="dynamic")

lazy="dynamic" 返回的是一个查询对象,而不是实际的 children 对象。为了使用 Pydantic 进行验证,你必须先执行这个查询来获取实际的对象。

lazy 加载策略总结

  • lazy="immediate": 立即加载相关对象,使其在 Pydantic 的 model_validate 中可用。
  • lazy="select": 需要在访问时加载相关对象,可能在验证时尚未加载。
  • lazy="dynamic": 返回查询对象,需要额外的查询步骤,model_validate 无法直接处理。

在 SQLAlchemy 中,lazy 模式是模型定义的一部分,决定了如何加载相关的对象。

为了确保 model_validate 正常工作,通常建议使用 lazy="immediate" 以确保所有相关数据在进行验证时已经被完全加载。

最后修改为lazy="immediate" 后,工作正常,顺利进行列表的转换了。

2、递归方式处理树形列表数据

为了确保 Pydantic 的 model_validate 能正确处理嵌套关系,推荐使用 lazy="immediate" 策略。这样可以保证在 Pydantic 进行数据验证时,所有相关数据已经完整加载。

除了这样对树形列表进行处理外,还有什么好办法处理?

处理树形列表(或树形结构)的常见方法包括以下几种,除了直接使用 SQLAlchemy 的 relationshiplazy="immediate" 加载策略外,还可以采用其他一些技术和工具来处理和操作树形数据结构。

下面介绍使用递归检索方式进行列表数据的处理。在 Python 中,递归处理树形列表的常见方法是使用递归函数遍历树结构。

假设你有一个部门(Ou)模型,每个部门可以有多个子部门(树形结构)。你想要使用递归方法遍历并填充树形结构中的每个节点。

通过 Pydantic 的 BaseModel 类进行遍历填充,可以结合递归和 Pydantic 的模型来处理树形结构的数据。以下是一个详细的示例,展示如何使用 Pydantic 的 BaseModel 类进行递归遍历和填充树形结构。

首先,定义一个 Pydantic 模型,用于表示树形结构中的节点。为了处理嵌套的子节点,可以在模型中使用递归类型注解。

from typing import List, Optional
from pydantic import BaseModel class OuNodeDto(BaseModel):
id: int
name: str
children: Optional[List['OuNodeDto']] = None # 递归类型注解 class Config:
orm_mode = True

假设我们有一组嵌套的字典数据,表示树形结构:

ou_data = [
{
"id": 1,
"name": "Root Department",
"children": [
{
"id": 2,
"name": "Child Department 1",
"children": [
{"id": 4, "name": "Grandchild Department 1"},
{"id": 5, "name": "Grandchild Department 2"}
]
},
{"id": 3, "name": "Child Department 2"}
]
}
]

通过 Pydantic 的 BaseModel,你可以直接进行递归填充。假设 ou_data 是从数据库或者其他外部来源获取的字典列表,你可以通过递归构造 OuNodeDto 实例。

def build_tree(data: List[dict]) -> List[OuNodeDto]:
"""
递归遍历并构建 Pydantic 模型树。 :param data: 包含树结构的字典列表
:return: 填充后的 Pydantic 模型列表
"""
tree = []
for node_data in data:
# 处理子节点递归填充
children = build_tree(node_data.get("children", [])) # 使用 Pydantic 的模型验证和创建对象
node = OuNodeDto(
id=node_data["id"],
name=node_data["name"],
children=children if children else None
)
tree.append(node)
return tree

使用上面的 build_tree 函数,你可以递归地遍历数据,并使用 Pydantic 模型来构建整个树形结构。

ou_tree = build_tree(ou_data)

# 输出树形结构的Pydantic模型
for node in ou_tree:
print(node)

运行上面的代码后,输出将是一个 Pydantic 模型的树形结构列表,已填充好所有数据。

OuNodeDto(id=1, name='Root Department', children=[
OuNodeDto(id=2, name='Child Department 1', children=[
OuNodeDto(id=4, name='Grandchild Department 1', children=None),
OuNodeDto(id=5, name='Grandchild Department 2', children=None)
]),
OuNodeDto(id=3, name='Child Department 2', children=None)
])

通过这种方式,你可以使用 Pydantic 的 BaseModel 结合递归函数来处理树形结构的数据填充和验证。Pydantic 提供了强大的数据验证和解析能力,使得处理复杂的嵌套结构变得更加容易。

如果是从数据库中检索获得的SqlAlchemy的模型类,应该如何递归遍历?

要从数据库中检索并递归遍历 SQLAlchemy 的模型类,然后将其转换为 Pydantic 的模型类,可以按照以下步骤进行操作。假设你已经定义了 SQLAlchemy 模型类和对应的 Pydantic 模型类,接下来将展示如何递归遍历和填充这些数据。

首先,我们定义一个包含自引用关系的 SQLAlchemy 模型。

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, declarative_base Base = declarative_base() class Ou(Base):
__tablename__ = "t_acl_ou" id = Column(Integer, primary_key=True, autoincrement=True)
pid = Column(Integer, ForeignKey("t_acl_ou.id"))
name = Column(String) # 自引用关系
parent = relationship("Ou", remote_side=[id], back_populates="children")
children = relationship("Ou", back_populates="parent")

然后定义一个对应的 Pydantic 模型,支持嵌套的子节点。

from typing import List, Optional
from pydantic import BaseModel class OuNodeDto(BaseModel):
id: int
name: str
children: Optional[List['OuNodeDto']] = None # 递归类型注解 class Config:
orm_mode = True # 允许从 ORM 对象转换

使用 SQLAlchemy 的查询从数据库中获取组织结构数据。为了处理嵌套的关系,你可以使用 selectinload 或其他类似的加载策略来预先加载子节点。

from sqlalchemy.future import select
from sqlalchemy.orm import selectinload, sessionmaker
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine # 假设使用异步引擎
engine = create_async_engine('sqlite+aiosqlite:///./test.db')
Session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async def get_ou_tree_by_id(ou_id: int):
async with Session() as session:
# 使用 selectinload 预先加载子节点
result = await session.execute(
select(Ou).options(selectinload(Ou.children)).filter_by(id=ou_id)
)
ou = result.scalars().first()
return ou

编写一个递归函数,将 SQLAlchemy 的 ORM 对象转换为 Pydantic 模型对象。

def build_tree(node: Ou) -> OuNodeDto:
"""
递归遍历 SQLAlchemy 模型,并转换为 Pydantic 模型。 :param node: SQLAlchemy 模型实例
:return: 填充后的 Pydantic 模型实例
"""
# 使用 Pydantic 模型验证和创建对象
node_dto = OuNodeDto(
id=node.id,
name=node.name,
# 递归填充子节点
children=[build_tree(child) for child in node.children] if node.children else None
)
return node_dto

结合以上步骤,从数据库中获取数据并构建树形结构。

import asyncio

async def main():
# 获取指定 ID 的组织结构树
ou = await get_ou_tree_by_id(1) # 递归构建树形结构的 Pydantic 模型
ou_tree = build_tree(ou) # 输出树形结构
print(ou_tree) # 运行异步任务
asyncio.run(main())

执行上述代码后,ou_tree 将是一个包含组织结构树的 Pydantic 模型对象,所有嵌套的子节点将被递归地转换和填充。

使用 Pydantic 的 model_validate 处理并且relationship加载策略lazy=immediate 和通过递归SqlAlchemy的模型类填充,哪个性能更好?

在比较使用 Pydantic 的 model_validate 与通过递归手动填充 SQLAlchemy 模型类的性能时,主要考量点是数据加载策略和模型转换方式的效率。以下是两者的详细分析:

1) Pydantic 的 model_validate + lazy="immediate"

  • 工作原理:

    • 使用 lazy="immediate" 会在 SQLAlchemy 实例化对象时立即加载关联的子对象。这意味着在访问主对象时,子对象已经完全加载,不需要再发起额外的数据库查询。
    • 使用 Pydantic 的 model_validate 可以将 SQLAlchemy 的 ORM 对象直接转换为 Pydantic 模型。
  • 性能考虑:

    • 数据加载: lazy="immediate" 会在访问对象时自动加载相关数据,适合需要立即访问完整数据结构的场景,但它可能会导致不必要的加载,尤其是在不需要所有子对象的情况下。
    • 转换开销: model_validate 是一个单步操作,自动处理复杂嵌套对象的转换。虽然方便,但它的开销取决于对象的复杂性和嵌套深度。对于深度嵌套的大量对象,转换的时间可能较长。
  • 优点:

    • 简洁:减少了手动编写递归代码的需要。
    • 自动化:能够自动处理嵌套关系的转换。
  • 缺点:

    • 可能会导致不必要的数据加载。
    • 对于大规模嵌套对象,性能可能下降。

2)递归手动填充 SQLAlchemy 模型类

  • 工作原理:

    • 手动编写递归函数来遍历 SQLAlchemy 的 ORM 对象并转换为 Pydantic 模型。
    • 可以灵活地选择何时加载子对象(通过 selectinload, joinedload 等),从而在需要时再加载数据。
  • 性能考虑:

    • 数据加载: 手动控制数据加载策略,可以优化查询性能,避免不必要的数据加载。只在需要时加载数据,避免了 lazy="immediate" 带来的过度加载。
    • 转换开销: 手动递归通常只处理当前节点及其直接子节点,相对高效。你可以通过优化递归逻辑,减少不必要的对象创建和转换。
  • 优点:

    • 性能优化:可以根据需要优化加载策略和递归处理,避免不必要的计算和数据加载。
    • 灵活性:更灵活地控制对象的转换和数据访问。
  • 缺点:

    • 需要手动编写递归逻辑,增加了代码复杂性。
    • 不如 model_validate 自动化,容易出错。

3)总结与选择

  • 数据规模和复杂性较小时: model_validate + lazy="immediate" 更方便,且性能影响不大,可以快速实现自动化的 Pydantic 模型转换。

  • 数据规模大且嵌套深度较高时: 手动递归填充可能会更高效,尤其是在你需要精细控制数据加载策略时。这样可以避免过度加载,并且优化性能。

结论:如果你的应用场景对性能要求较高,并且数据结构较为复杂,手动递归可能更优。如果优先考虑代码的简洁性和开发效率,且数据规模不大,那么 model_validate 配合 lazy="immediate" 是更好的选择。

使用Pydantic和SqlAlchemy实现树形列表数据(自引用表关系)的处理,以及递归方式处理数据差异的更多相关文章

  1. 妙用Excel数据透视表和透视图向导,将二维数据转换为一维数据

    项目中,每年都会有各种经销商的各种产品目标数据导入,经销商和产品过多,手工操作过于单调和复杂.那有没有一种方式可以将复杂的二维数据转换为一维数据呢? 有,强大的Excel就支持此功能. 常用Excel ...

  2. 【Excle数据透视表】如何创建非共享缓存的数据透视表

    一般情况下,利用同一个数据源创建多个数据表时,默认创建的是共享缓存的数据透视表.刷新一个数据透视表时会影响其他数据透视表的展示结果. 解决方案 创建非共享缓存的多个数据透视表 步骤一 单击工作表数据任 ...

  3. 【Excle数据透视表】如何显示/隐藏数据透视表字段列表

    在创建完毕的数据透视表中,若单击数据透视表中任意单元格,即可显示数据透视表字段列表窗格,用户就可以通过弹出的窗格进行字段调整 两种情况需要隐藏数据数据透视表窗格 ①数据透视表已经完成 ②失误关闭窗格 ...

  4. 大数据量表中,增加一个NOT NULL的新列

      这次,发布清洗列表功能,需要对数据库进行升级.MailingList表加个IfCleaning字段,所有的t_User*表加个IfCleaned字段.   脚本如下 对所有的t_User表执行 a ...

  5. Excel 批量快速合并相同的单元格:数据透视表、宏代码、分类汇总

    Excel 批量快速合并相同的单元格   在制作Excel表格的时候,为了使得自己制作的报表更加简洁明了,方便查阅,经常需要合并很多相同的单元格,如果有几千几万条记录需要合并的话,真的会让人发疯.怎样 ...

  6. Excel应用程序如何创建数据透视表

    原文作者:andreww 原文链接: http://blogs.msdn.com/andreww/archive/2008/07/25/creating-a-pivottable-programmat ...

  7. Excel2010数据透视表1

    “透视”作为一个动词,意思是旋转.如果将数据看成是一个物体,数据透视表允许旋转数据汇总,从不同角度或观点来看它.数据透视表能够轻松地移动字段,交换字段位置,设置创建项目的特定组. 如果给出一个陌生的物 ...

  8. EXCEL 2010学习笔记 —— 数据透视表

    今天整理一下EXCEL2010 数据透视表的课程笔记,数据透视表可以对多组数据进行统计和整理,是一种基本的数据可视化工具. 记录6个方面的总结: 1.创建数据透视表 2.更改数据透视表的汇总方式 3. ...

  9. 【转载】使用Pandas创建数据透视表

    使用Pandas创建数据透视表 本文转载自:蓝鲸的网站分析笔记 原文链接:使用Pandas创建数据透视表 目录 pandas.pivot_table() 创建简单的数据透视表 增加一个行维度(inde ...

  10. VSTO学习笔记(十四)Excel数据透视表与PowerPivot

    原文:VSTO学习笔记(十四)Excel数据透视表与PowerPivot 近期公司内部在做一种通用查询报表,方便人力资源分析.统计数据.由于之前公司系统中有一个类似的查询使用Excel数据透视表完成的 ...

随机推荐

  1. 【Shiro】01 概述 & 快速上手

    什么是Shiro? Apache Shiro 是Java的一个权限安全框架 一些功能:认证.授权.加密.会话管理.与Web 集成.缓存等   Shiro官网地址:[ 点击访问 ] http://shi ...

  2. 推荐一款好用的PDF转换工具,可以拆分、合并,亲测好用!!!

    推荐一款好用的PDF转换工具,可以拆分.合并,等等操作,亲测好用. PS. 因为经常会遇到PDF的拆分需要,以前在网上的都是免费的,后来的也都变成付费的无水印的了,再然后就变成全都要收费了.尴尬的是, ...

  3. tensorflow 读、存取 图像 数据的 TFRecord 方法 (示例)

    1.     利用TFRecord 格式   读.存 取    Mnist数据集的方法 存取   Mnist数据集的方法     (TFRecord格式) import tensorflow as t ...

  4. 获取客户端真实IP备忘

    出于安全考虑,近期在处理一个记录用户真实IP的需求.本来以为很简单,后来发现没有本来以为的简单.这里主要备忘下,如果服务器处于端口回流(hairpin NAT),keepalived,nginx之后, ...

  5. SenseCraft 部署模型到Grove Vision AI V2图像处理模块

    Grove Vision AI V2 图像处理模块开箱测评 摘要 今天教大家快速上手 Grove Vision AI V2 图像处理模块,我们将一起探讨如何利用 SenseCraft 部署 AI 模型 ...

  6. 一次生产环境mysql迁移操作(二)mysql空间释放(碎片整理)

    一次生产环境mysql迁移操作(一)数据归档 一次生产环境mysql迁移操作(二)mysql空间释放(碎片整理) 上文中增加了定时归档,现在一些大表磁盘空间一直不释放,导致数据库文件越来越大.现在介绍 ...

  7. 【CMake系列】10-cmake测试 ctest

    cmake作为一个强大的构建系统指导工具,同时也提供了测试功能,可用于项目的单元测试等,也可以与其他测试框架协作,如googletest,共同完成项目开发中的测试工作,本节我们就来学习 如何借助cma ...

  8. QTreeWidget绑定QTabWidget

    QTreeWidget绑定QTabWidget 本文仅供本人知识总结使用,所以内容会比较浅显,不喜勿喷. 文章目录 QTreeWidget绑定QTabWidget 一.通过treeWidget路径配合 ...

  9. 组合逻辑环(Combinational Logic Loop)

    组合逻辑电路 组合逻辑电路是数字电子学中一类基本的电路类型,它由一系列逻辑门组成,用于实现特定的逻辑功能.与时序逻辑电路不同,组合逻辑电路的输出完全取决于当前的输入信号,而不受之前输入的影响.换句话说 ...

  10. 【VMware VCF】VCF 5.2:挂载远程 vSAN 数据存储。

    VMware vSAN 解决方案中,为了充分利用 vSAN HCI 集群内的存储资源, vSAN HCI 和 vSAN HCI 集群之间可以相互共享存储资源,这种解决方案早期叫 vSAN HCI Me ...