You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core读取SQL View无法填充嵌套DTO对象问题排查

问题分析与解决方案

核心问题原因

  • EF Core的HasMany/WithOne实体关系配置是用于多表/多视图之间的关联查询,而你的vw_SupplierInformation是一个扁平化的单视图,所有字段都在同一行中,EF无法自动将单视图的扁平字段拆分成嵌套的DTO对象。
  • 你使用FromSqlRaw直接查询视图时,没有触发EF的导航属性加载逻辑;即使使用Include,EF也无法从单一行数据中生成集合类型的嵌套对象(因为单视图的每一行是供应商+客户配置+服务的组合数据,不是关联的多数据源)。
  • DTO的导航属性配置不符合EF预期:BusinessClientConfigurationViewDto中的AllocationSupplierViewDto标记为required,但视图中没有对应的外键关联逻辑,EF无法自动填充这个反向引用。

解决方案

方案一:手动映射扁平数据到嵌套DTO(推荐)

因为视图是扁平化结构,最直接的方式是先查询得到扁平数据,再手动将字段拆分并组装成嵌套的DTO结构。

步骤1:创建扁平实体类对应视图字段

定义一个与视图字段完全匹配的扁平实体,用于接收视图数据:

public class SupplierInformationFlat
{
    public Guid Id { get; set; }
    public string? SupplierName { get; set; }
    public string? EmailAddress { get; set; }
    public string? PhoneNumber { get; set; }
    public double? CsatRating { get; set; }
    public int? CsatRatingCount { get; set; }
    public double? Latitude { get; set; }
    public double? Longitude { get; set; }
    public string? AddressLine1 { get; set; }
    public string? AddressLine2 { get; set; }
    public string? AddressLine3 { get; set; }
    public string? City { get; set; }
    public string? CountryIsoNumber { get; set; }
    public string? StateProvinceCounty { get; set; }
    public string? PostCode { get; set; }
    public List<Guid>? PartnersOnHoldIds { get; set; }
    public Guid BusinessClientId { get; set; }
    public int Status { get; set; }
    public Guid ServiceTypeId { get; set; }
    public DateTime? StartDT { get; set; }
    public DateTime? EndDT { get; set; }
    public bool MaxCapacityExceeded { get; set; }
    public Guid? CapabilityId { get; set; }
    public Guid? NetworkId { get; set; }
    public Guid? PartnerId { get; set; }
}

步骤2:配置DbContext

在DbContext中添加该实体的DbSet,并映射到视图:

public DbSet<SupplierInformationFlat> SupplierInformationFlat { get; set; }

protected override void OnModelCreating(ModelBuilder builder)
{
    // 保留原有其他配置
    builder.Entity<SupplierInformationFlat>()
        .ToView("vw_SupplierInformation")
        .HasKey(e => e.Id);
    base.OnModelCreating(builder);
}

步骤3:查询并手动映射

修改查询方法,先获取扁平数据,再通过分组、选择组装成嵌套DTO:

public async Task<IEnumerable<AllocationSupplierViewDto>> GetAllocationSuppliersByIdAsync(Guid id)
{
    var flatData = await dbContext.SupplierInformationFlat
        .Where(s => s.Id == id)
        .ToListAsync();

    // 分组组装嵌套DTO
    var result = flatData
        .GroupBy(s => s.Id)
        .Select(supplierGroup => new AllocationSupplierViewDto
        {
            Id = supplierGroup.Key,
            SupplierName = supplierGroup.First().SupplierName,
            EmailAddress = supplierGroup.First().EmailAddress,
            PhoneNumber = supplierGroup.First().PhoneNumber,
            CsatRating = supplierGroup.First().CsatRating,
            CsatRatingCount = supplierGroup.First().CsatRatingCount,
            Latitude = supplierGroup.First().Latitude,
            Longitude = supplierGroup.First().Longitude,
            AddressLine1 = supplierGroup.First().AddressLine1,
            AddressLine2 = supplierGroup.First().AddressLine2,
            AddressLine3 = supplierGroup.First().AddressLine3,
            City = supplierGroup.First().City,
            CountryIsoNumber = supplierGroup.First().CountryIsoNumber,
            StateProvinceCounty = supplierGroup.First().StateProvinceCounty,
            Postcode = supplierGroup.First().PostCode,
            PartnersOnHoldIds = supplierGroup.First().PartnersOnHoldIds,
            BusinessClientConfiguration = supplierGroup
                .GroupBy(s => s.BusinessClientId)
                .Select(bcGroup => new BusinessClientConfigurationViewDto
                {
                    BusinessClientId = bcGroup.Key,
                    Status = bcGroup.First().Status,
                    AllocationSupplierViewDtoId = supplierGroup.Key,
                    AllocationSupplierViewDto = null!, // 可修改DTO去掉required,或后续关联父对象
                    Services = bcGroup
                        .GroupBy(s => s.ServiceTypeId)
                        .Select(serviceGroup => new ServiceViewDto
                        {
                            ServiceTypeId = serviceGroup.Key,
                            StartDT = serviceGroup.First().StartDT,
                            EndDT = serviceGroup.First().EndDT,
                            MaxCapacityExceeded = serviceGroup.First().MaxCapacityExceeded,
                            CapabilityIds = serviceGroup
                                .Where(s => s.CapabilityId.HasValue)
                                .Select(s => s.CapabilityId.Value)
                                .Distinct()
                                .ToList(),
                            Networks = serviceGroup
                                .Where(s => s.NetworkId.HasValue)
                                .GroupBy(s => s.NetworkId.Value)
                                .Select(networkGroup => new NetworkViewDto
                                {
                                    NetworkId = networkGroup.Key,
                                    PartnerIds = networkGroup
                                        .Where(s => s.PartnerId.HasValue)
                                        .Select(s => s.PartnerId.Value)
                                        .Distinct()
                                        .ToList()
                                }).ToList()
                        }).ToList()
                }).ToList()
        }).ToList();

    return result;
}

方案二:拆分视图并配置EF关联(可选)

如果希望EF自动处理嵌套,可以将原视图拆分为多个关联视图(比如供应商视图、客户配置视图、服务视图),并在视图之间建立外键关联,然后配置EF实体关系,使用Include加载导航属性。但这种方式需要修改数据库视图结构,适合视图数据本身存在多对多/一对多关系的场景。

注意事项

  • 若使用AutoMapper,可以配置映射规则替代手动映射,减少重复代码。
  • 原DTO中required AllocationSupplierViewDto AllocationSupplierViewDto建议调整为init修饰符或去掉required,避免手动映射时的赋值困难。
  • 视图中可能存在重复数据,需通过GroupBy和Distinct确保嵌套对象的唯一性。

内容的提问来源于stack exchange,提问作者Robert Daraż

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 15:55:57