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

如何使用Lambda表达式替代循环处理三层关联DTO的批量提交?

批量提交嵌套关联数据:用Lambda替代foreach实现

问题背景

我有三个类:Header、Property、Units,其中Header与Property为一对多关联,Property与Units为一对多关联。对应的DTO代码如下:

Header DTO

public class HeaderDTO
{
    public string Phone { get; set; } = null!;

    public string? Source { get; set; }

    public string? Address { get; set; }

    public string? Country { get; set; }

    public string? ShipTo { get; set; }

    public virtual ICollection<PropertyDTO> Properties { get; set; } = new List<PropertyDTO>();
}

Property DTO

public class PropertyDTO
{
    public string? Floor { get; set; }

    public string? Area { get; set; }

    public int Window { get; set; }

    public string Products { get; set; } = null!;

    public string? Login { get; set; }

    public string? ShipTo { get; set; }

    public int? Flag { get; set; }

    public virtual ICollection<UnitDTO> Units { get; set; } = new List<UnitDTO>();
}

Units DTO

public class UnitDTO
{
    public string? UniqueName { get; set; }

    public int Height { get; set; }

    public int Width { get; set; }

    public int Panel { get; set; }

    public int? Belah { get; set; }

    public string? Img1 { get; set; }

    public string? Img2 { get; set; }

    public string? Img3 { get; set; }

    public List<TDataProduct>? products;
}

我需要批量提交Property和Units的JSON记录,目前已通过foreach循环实现功能(如下代码所示),但希望使用Lambda表达式替代循环实现相同逻辑,此前尝试调用Include函数无效,寻求技术指导。

现有接口代码:

[HttpPost("SOPPOST")]
public async Task<ActionResult<TDataHeader>> CreateHeaders(HeaderDTO hdto)
{
    var header = new HeaderDTO
    {
        Address = hdto.Address,
        Country = hdto.Country,
        Phone = hdto.Phone,
        ShipTo = hdto.ShipTo,
        Source = hdto.Source
    };
    var phoneVerify = _context.TDataHeaders.Count(x => x.Phone == header.Phone);
    var headerImport = new TDataHeader
    {
        Address = header.Address,
        Country = header.Country,
        Phone = header.Phone,
        ShipTo = header.ShipTo,
        Source = header.Source
    };
    //if (phoneVerify == 0)
    //{
    //    await _context.TDataHeaders.AddAsync(headerImport);
    //}
    foreach (var prop in hdto.Properties)
    {
        var property = new TDataProperty
        {
            Floor = prop.Floor,
            Area = prop.Area,
            Windows = prop.Windows,
            Flag = prop.Flag,
            Login = prop.Login,
            Products = prop.Products,
            ShipTo = prop.ShipTo,
            Header = headerImport
        };
        foreach (var unit in prop.Units)
        {
            var unitModel = new TDataUnit
            {
                Belah = unit.Belah,
                Height = unit.Height,
                Width = unit.Width,
                Panel = unit.Panel,
                Img1 = unit.Img1,
                Img2 = unit.Img2,
                Img3 = unit.Img3,
                UniqueName = prop.Floor + "-" + prop.Area + "-" + prop.Windows,
                Property = property
            };
            await _context.TDataUnits.AddRangeAsync(unitModel);
        }
        await _context.TDataProperties.AddRangeAsync(property);
    }

    await _context.SaveChangesAsync();
    return Ok("Posted");
}

解决方案

首先明确:Include是Entity Framework中查询阶段加载关联实体的方法,和数据插入逻辑完全无关,所以这里用它解决批量插入问题是找错了方向。

要通过Lambda表达式(结合LINQ的Select、SelectMany)替代foreach循环,核心思路是将DTO集合转换为实体集合,再批量添加到上下文。

修改后的接口代码如下:

[HttpPost("SOPPOST")]
public async Task<ActionResult<TDataHeader>> CreateHeaders(HeaderDTO hdto)
{
    var headerImport = new TDataHeader
    {
        Address = hdto.Address,
        Country = hdto.Country,
        Phone = hdto.Phone,
        ShipTo = hdto.ShipTo,
        Source = hdto.Source
    };

    // 验证手机号是否存在(原逻辑保留)
    var phoneExists = await _context.TDataHeaders.AnyAsync(x => x.Phone == headerImport.Phone);
    //if (!phoneExists)
    //{
    //    await _context.TDataHeaders.AddAsync(headerImport);
    //}

    // 将DTO转换为Property实体集合,同时生成对应的Unit实体
    var propertyEntities = hdto.Properties.Select(prop => new TDataProperty
    {
        Floor = prop.Floor,
        Area = prop.Area,
        Windows = prop.Window, // 注意原DTO中是Window,实体是Windows,需匹配字段名
        Flag = prop.Flag,
        Login = prop.Login,
        Products = prop.Products,
        ShipTo = prop.ShipTo,
        Header = headerImport,
        // 直接生成关联的Unit实体集合
        Units = prop.Units.Select(unit => new TDataUnit
        {
            Belah = unit.Belah,
            Height = unit.Height,
            Width = unit.Width,
            Panel = unit.Panel,
            Img1 = unit.Img1,
            Img2 = unit.Img2,
            Img3 = unit.Img3,
            UniqueName = $"{prop.Floor}-{prop.Area}-{prop.Window}",
            Property = property // 关联到当前创建的Property实体
        }).ToList()
    }).ToList();

    // 批量添加Property,EF会自动处理关联的Unit实体,无需单独添加
    await _context.TDataProperties.AddRangeAsync(propertyEntities);
    await _context.SaveChangesAsync();

    return Ok("Posted");
}

关键说明

  1. LINQ转换逻辑:用Select将每个PropertyDTO转换为TDataProperty,内部嵌套Select完成UnitDTO到TDataUnit的转换并建立关联。
  2. 批量操作效率:一次性添加整个Property集合,相比原代码多次调用AddRangeAsync更高效,EF会自动追踪并插入关联的Unit实体。
  3. 字段匹配注意:原DTO中PropertyDTO的字段是Window(单数),实体TDataProperty是Windows(复数),转换时需确保字段名一致,避免赋值错误。
  4. Include的正确用法:Include仅用于查询场景加载关联数据,插入/更新时依靠EF对导航属性的自动追踪即可,无需调用该方法。

内容的提问来源于stack exchange,提问作者Sam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 01:20:57