SelectAll 投影到 DTO/VO 时,数据库列名与属性名不一致的字段无法映射 返回

SqlSugar 待处理
198
该叫什么 . 发布于2周前
悬赏:5 飞吻

问题咨询:SelectAll 投影到 DTO/VO 时,数据库列名与属性名不一致的字段无法映射

你好,我不确定这是我没有找到对应文档,还是这个场景目前不支持,或者确实缺少相关处理,所以想先提一个问题咨询一下。

问题概述

在联表查询中使用 SelectAll(),并且把查询结果直接投影到 DTO/VO 类型时,如果某些字段的数据库列名和 C# 属性名不一致,这些字段不会被正确赋值。

实体类上有 [SugarColumn(ColumnName = "...")],所以直接查询实体类本身时可以正常映射。但是 DTO/VO 类没有,也不希望加 SqlSugar 的特性。此时 SelectAll() 似乎返回的是数据库列名,而 DTO/VO 结果绑定时按属性名匹配,导致字段值丢失。

示例

数据库表

CREATE TABLE OrderLine (
   Id INT,
   ProductCode NVARCHAR(50),
   EXT_MaterialCode NVARCHAR(50),
   EXT_Thickness DECIMAL(18, 4)
);

CREATE TABLE Product (
   ProductCode NVARCHAR(50),
   ImageUrl NVARCHAR(500)
);

实体类

[SugarTable("OrderLine")]
public class OrderLineEntity
{
   [SugarColumn(ColumnName = "Id")]
   public int? Id { get; set; }

   [SugarColumn(ColumnName = "ProductCode")]
   public string? ProductCode { get; set; }

   [SugarColumn(ColumnName = "EXT_MaterialCode")]
   public string? MaterialCode { get; set; }

   [SugarColumn(ColumnName = "EXT_Thickness")]
   public decimal? Thickness { get; set; }
}

[SugarTable("Product")]
public class ProductEntity
{
   [SugarColumn(ColumnName = "ProductCode")]
   public string? ProductCode { get; set; }

   [SugarColumn(ColumnName = "ImageUrl")]
   public string? ImageUrl { get; set; }
}

DTO / VO

DTO/VO 是接口或视图模型,不希望依赖 ORM 特性:

public class OrderLineDto
{
   public int Id { get; set; }
   public string ProductCode { get; set; }
   public string MaterialCode { get; set; }
   public decimal Thickness { get; set; }

   public string ImageUrl { get; set; }
}

查询代码

var list = await db.Queryable<OrderLineEntity>("line")
   .LeftJoin<ProductEntity>((line, product) => line.ProductCode == product.ProductCode)
   .Select((line, product) => new OrderLineDto
   {
       Id = line.Id.SelectAll(),
       ImageUrl = product.ImageUrl
   })
   .ToListAsync();

实际结果

数据库列名和 C# 属性名一致的字段可以正常赋值:

Id           // 正常
ProductCode // 正常
ImageUrl    // 正常

但数据库列名和 C# 属性名不一致的字段没有被正确赋值:

MaterialCode // null,但数据库列 EXT_MaterialCode 有值
Thickness    // 默认值,但数据库列 EXT_Thickness 有值

期望结果

因为 SelectAll() 是从 line 调用的,而 line 的类型是 OrderLineEntity,实体类上已经有 [SugarColumn] 映射,所以我原本期望 SqlSugar 可以根据源实体的映射信息生成别名,例如:

line.EXT_MaterialCode AS MaterialCode,
line.EXT_Thickness AS Thickness

这样查询结果就可以直接投影到 OrderLineDto,而不需要在 DTO/VO 上再加 [SugarColumn]

为什么不希望在 DTO/VO 上加 SugarColumn

很多项目中实体类和 DTO/VO 的职责是分开的:

  • 实体类用于描述数据库结构,可以包含 ORM 特性。

  • DTO/VO 用于接口返回、视图展示或客户端模型,不希望依赖 ORM。

  • 同一个 DTO/VO 可能会被序列化、前端、客户端或其他层复用。

因此,把 [SugarColumn] 加到 DTO/VO 上并不总是合适。

我尝试或考虑过的 workaround

1. 先查询实体类,再手动映射到 DTO/VO

这样可以让实体类上的 [SugarColumn] 生效,但如果还需要联表字段,就需要额外处理。

var rows = await db.Queryable<OrderLineEntity>("line")
   .LeftJoin<ProductEntity>((line, product) => line.ProductCode == product.ProductCode)
   .Select((line, product) => new SomeIntermediateType
   {
       // 在这里组合 line 和 product 的字段
   })
   .ToListAsync();

2. 显式写出所有字段

这种方式可靠,但当主表字段很多时会非常繁琐:

.Select((line, product) => new OrderLineDto
{
   Id = line.Id,
   ProductCode = line.ProductCode,
   MaterialCode = line.MaterialCode,
   Thickness = line.Thickness,
   ImageUrl = product.ImageUrl
})

3. 给 DTO/VO 注册运行时映射

例如:

db.MappingColumns.Add("MaterialCode", "EXT_MaterialCode", nameof(OrderLineDto));
db.MappingColumns.Add("Thickness", "EXT_Thickness", nameof(OrderLineDto));

这种方式可能可以解决问题,但我不确定这是不是推荐做法。它会改变当前 SqlSugar Context 中该 DTO 类型的映射行为,如果 DTO/VO 后续也被其他查询或操作使用,可能会有副作用。

想咨询的问题

  1. 是否有已存在的文档或推荐写法,可以让 SelectAll() 使用源实体类的 [SugarColumn] 映射,并自动给目标 DTO/VO 属性生成别名?

  2. 如果没有,这种“源实体有列映射,目标 DTO/VO 无 ORM 特性”的场景是否属于不支持的设计?

  3. SelectAll() 在投影到不同结果类型时,是否可以考虑基于源实体映射自动生成 AS 属性名

  4. 当主表字段很多,同时又需要少量联表字段时,推荐的写法是什么?

环境

  • SqlSugar 版本:5.1.2.7

  • 数据库:SQL Server

  • 运行环境:.NET 6



热忱回答0