痞子秦 發表於 2025-4-8 22:33:00

经过几天的努力Biwen.AutoClassGen终于实现了DTO复杂属性的生成

<h2 id="前言">前言</h2>
<p>距写上一篇 https://www.cnblogs.com/vipwan/p/18535459 生成DTO已经有一段时间了,</p>
<p>最初<s>没有考虑</s>复杂二级属性嵌套的实现,而是直接使用排除使用自定义的方式解决这个问题,</p>
<p>但是这个有些违背简约到底的初衷,并且也有好几个网友提出希望支持复杂嵌套属性DTO的功能,最近自己凑了几天时间打磨了一下,算是大致实现了这个功能</p>
<h3 id="使用方式">使用方式</h3>
<h4 id="支持特性继承">支持特性继承</h4>
<p>比如我们的DTO对象也需要<code></code>,<code></code>等校验型特征,我们只需要目标类的属性标注即可,生成的DTO也将传递这些重要的特性,对于<code>OpenApi</code>文档以及一些验证场景会相当有帮助:<br>
<img src="https://img2024.cnblogs.com/blog/127598/202504/127598-20250408224147691-1124397674.png" alt="image" loading="lazy"></p>
<pre><code class="language-csharp">public class Person
{
   
    public string Name { get; set; } = string.Empty;
   
    public int Age { get; set; }
}
</code></pre>
<p>生成的DTO:</p>
<pre><code class="language-csharp">/// &lt;inheritdoc cref = "Person.Name"/&gt;

public string Name { get; set; }
/// &lt;inheritdoc cref = "Person.Age"/&gt;


public int Age { get; set; }
</code></pre>
<h4 id="支持复杂属性嵌套生成">支持复杂属性嵌套生成</h4>
<blockquote>
<p>实体定义示例:</p>
</blockquote>
<pre><code class="language-csharp">// 主实体
public class Person
{
   
    public string Name { get; set; } = string.Empty;
   
    public int Age { get; set; }
    // 嵌套对象
    public Address Address { get; set; } = new();
    // 集合属性
    public List&lt;Hobby&gt; Hobbies { get; set; } = [];
    // 使用特性标记忽略的属性
   
    public string Igrone2 { get; set; } = null!;
}

// 嵌套实体
public class Address
{
   
    public string Street { get; set; } = string.Empty;
   
    public string City { get; set; } = string.Empty;
   
    public string State { get; set; } = string.Empty;
   
    public string ZipCode { get; set; } = string.Empty;
}

// 集合项实体
public class Hobby
{
   
    public string Name { get; set; } = string.Empty;
   
    public string Description { get; set; } = string.Empty;
    // 多层嵌套
    public HobbyExtend Extend { get; set; } = new();
}

public class HobbyExtend
{
    public string Extend1 { get; set; } = string.Empty;
    public string Extend2 { get; set; } = string.Empty;
    public InnerExtend Extend3 { get; set; } = new();
}

public class InnerExtend
{
    public string InnerExtendMsg { get; set; } = string.Empty;
}
</code></pre>
<blockquote>
<ol>
<li>普通 DTO(单层映射)</li>
</ol>
</blockquote>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// 没有复杂属性嵌套的 DTO
/// &lt;/summary&gt;
//忽略掉Igrone属性
public partial record PersonDto;
</code></pre>
<blockquote>
<ol start="2">
<li>复杂 DTO(多层嵌套)</li>
</ol>
</blockquote>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// 模拟的复杂 DTO
/// &lt;/summary&gt;

//≥2即表示多层嵌套生成
public partial record PersonComplexDto;
</code></pre>
<h3 id="生成的代码样例">生成的代码样例:</h3>
<p>生成DTO,并对象生成映射扩展:<code>MapperToXXX</code>,以及IQuerylable扩展<code>ProjectToXXX</code>:<br>
并且生成了预留<code>partial</code>扩展,如果存在<code>FirstName + LastName -&gt; FullName</code>这种情况,你可以自己实现partial部分!</p>
<pre><code class="language-csharp">
// &lt;auto-generated /&gt;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

#pragma warning disable

//generate Person-PersonComplexDto
namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;

    public partial record class PersonComplexDto
    {
      /// &lt;inheritdoc cref = "Person.Name"/&gt;
      
      public string Name { get; set; }

      /// &lt;inheritdoc cref = "Person.Age"/&gt;
      
      
      public int Age { get; set; }
      /// &lt;inheritdoc cref = "Person.Address"/&gt;
      public AddressDto Address { get; set; }
      /// &lt;inheritdoc cref = "Person.Hobbies"/&gt;
      public System.Collections.Generic.List&lt;HobbyDto&gt; Hobbies { get; set; }
    }
}

namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.Linq;

    public static partial class PersonToPersonComplexDtoExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(Person from, PersonComplexDto to);
      /// &lt;summary&gt;
      /// mapper to PersonComplexDto
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static PersonComplexDto MapperToPersonComplexDto(this Person model)
      {
            if (model == null)
                return null;
            var retn = new PersonComplexDto()
            {
                Name = model.Name,
                Age = model.Age,
                Address = model.Address?.MapperToAddressDto(),
                Hobbies = model.Hobbies != null ? model.Hobbies.Select(x =&gt; x?.MapperToHobbyDto()).ToList() : null,
            };
            MapperToPartial(model, retn);
            return retn;
      }

      /// &lt;summary&gt;
      /// ProjectTo PersonComplexDto
      /// &lt;/summary&gt;
      public static IQueryable&lt;PersonComplexDto&gt; ProjectToPersonComplexDto(this IQueryable&lt;Person&gt; query)
      {
            return query.Select(model =&gt; model.MapperToPersonComplexDto());
      }
    }

    public static partial class PersonComplexDtoToPersonExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(PersonComplexDto from, Person to);
      /// &lt;summary&gt;
      /// mapper to Person
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static Person MapperToPerson(this PersonComplexDto model)
      {
            if (model == null)
                return null;
            var retn = new Person()
            {
                Name = model.Name,
                Age = model.Age,
                Address = model.Address?.MapperToAddress(),
                Hobbies = model.Hobbies != null ? model.Hobbies.Select(x =&gt; x?.MapperToHobby()).ToList() : null,
            };
            MapperToPartial(model, retn);
            return retn;
      }
    }
}

//generate Person-PersonDto
namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;

    public partial record class PersonDto
    {
      /// &lt;inheritdoc cref = "Person.Name"/&gt;
      
      public string Name { get; set; }

      /// &lt;inheritdoc cref = "Person.Age"/&gt;
      
      
      public int Age { get; set; }
      /// &lt;inheritdoc cref = "Person.Address"/&gt;
      public Biwen.AutoClassGen.TestConsole.Dtos.Address Address { get; set; }
      /// &lt;inheritdoc cref = "Person.Hobbies"/&gt;
      public System.Collections.Generic.List&lt;Biwen.AutoClassGen.TestConsole.Dtos.Hobby&gt; Hobbies { get; set; }
    }
}

namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.Linq;

    public static partial class PersonToPersonDtoExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(Person from, PersonDto to);
      /// &lt;summary&gt;
      /// mapper to PersonDto
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static PersonDto MapperToPersonDto(this Person model)
      {
            if (model == null)
                return null;
            var retn = new PersonDto()
            {
                Name = model.Name,
                Age = model.Age,
                Address = model.Address,
                Hobbies = model.Hobbies,
            };
            MapperToPartial(model, retn);
            return retn;
      }

      /// &lt;summary&gt;
      /// ProjectTo PersonDto
      /// &lt;/summary&gt;
      public static IQueryable&lt;PersonDto&gt; ProjectToPersonDto(this IQueryable&lt;Person&gt; query)
      {
            return query.Select(model =&gt; model.MapperToPersonDto());
      }
    }

    public static partial class PersonDtoToPersonExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(PersonDto from, Person to);
      /// &lt;summary&gt;
      /// mapper to Person
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static Person MapperToPerson(this PersonDto model)
      {
            if (model == null)
                return null;
            var retn = new Person()
            {
                Name = model.Name,
                Age = model.Age,
                Address = model.Address,
                Hobbies = model.Hobbies,
            };
            MapperToPartial(model, retn);
            return retn;
      }
    }
}

//generate Address-AddressDto
namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;

    public partial class AddressDto
    {
      /// &lt;inheritdoc cref = "Address.Street"/&gt;
      
      public string Street { get; set; }

      /// &lt;inheritdoc cref = "Address.City"/&gt;
      
      public string City { get; set; }

      /// &lt;inheritdoc cref = "Address.State"/&gt;
      
      public string State { get; set; }

      /// &lt;inheritdoc cref = "Address.ZipCode"/&gt;
      
      public string ZipCode { get; set; }
    }
}

namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.Linq;
    using System.Net;

    public static partial class AddressToAddressDtoExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(Address from, AddressDto to);
      /// &lt;summary&gt;
      /// mapper to AddressDto
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static AddressDto MapperToAddressDto(this Address model)
      {
            if (model == null)
                return null;
            var retn = new AddressDto()
            {
                Street = model.Street,
                City = model.City,
                State = model.State,
                ZipCode = model.ZipCode,
            };
            MapperToPartial(model, retn);
            return retn;
      }

      /// &lt;summary&gt;
      /// ProjectTo AddressDto
      /// &lt;/summary&gt;
      public static IQueryable&lt;AddressDto&gt; ProjectToAddressDto(this IQueryable&lt;Address&gt; query)
      {
            return query.Select(model =&gt; model.MapperToAddressDto());
      }
    }

    public static partial class AddressDtoToAddressExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(AddressDto from, Address to);
      /// &lt;summary&gt;
      /// mapper to Address
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static Address MapperToAddress(this AddressDto model)
      {
            if (model == null)
                return null;
            var retn = new Address()
            {
                Street = model.Street,
                City = model.City,
                State = model.State,
                ZipCode = model.ZipCode,
            };
            MapperToPartial(model, retn);
            return retn;
      }
    }
}

//generate Hobby-HobbyDto
namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;

    public partial class HobbyDto
    {
      /// &lt;inheritdoc cref = "Hobby.Name"/&gt;
      
      public string Name { get; set; }

      /// &lt;inheritdoc cref = "Hobby.Description"/&gt;
      
      public string Description { get; set; }
      /// &lt;inheritdoc cref = "Hobby.Extend"/&gt;
      public HobbyExtendDto Extend { get; set; }
    }
}

namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.Linq;

    public static partial class HobbyToHobbyDtoExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(Hobby from, HobbyDto to);
      /// &lt;summary&gt;
      /// mapper to HobbyDto
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static HobbyDto MapperToHobbyDto(this Hobby model)
      {
            if (model == null)
                return null;
            var retn = new HobbyDto()
            {
                Name = model.Name,
                Description = model.Description,
                Extend = model.Extend?.MapperToHobbyExtendDto(),
            };
            MapperToPartial(model, retn);
            return retn;
      }

      /// &lt;summary&gt;
      /// ProjectTo HobbyDto
      /// &lt;/summary&gt;
      public static IQueryable&lt;HobbyDto&gt; ProjectToHobbyDto(this IQueryable&lt;Hobby&gt; query)
      {
            return query.Select(model =&gt; model.MapperToHobbyDto());
      }
    }

    public static partial class HobbyDtoToHobbyExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(HobbyDto from, Hobby to);
      /// &lt;summary&gt;
      /// mapper to Hobby
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static Hobby MapperToHobby(this HobbyDto model)
      {
            if (model == null)
                return null;
            var retn = new Hobby()
            {
                Name = model.Name,
                Description = model.Description,
                Extend = model.Extend?.MapperToHobbyExtend(),
            };
            MapperToPartial(model, retn);
            return retn;
      }
    }
}

//generate HobbyExtend-HobbyExtendDto
namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;

    public partial class HobbyExtendDto
    {
      /// &lt;inheritdoc cref = "HobbyExtend.Extend1"/&gt;
      public string Extend1 { get; set; }
      /// &lt;inheritdoc cref = "HobbyExtend.Extend2"/&gt;
      public string Extend2 { get; set; }
      /// &lt;inheritdoc cref = "HobbyExtend.Extend3"/&gt;
      public InnerExtendDto Extend3 { get; set; }
    }
}

namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.Linq;

    public static partial class HobbyExtendToHobbyExtendDtoExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(HobbyExtend from, HobbyExtendDto to);
      /// &lt;summary&gt;
      /// mapper to HobbyExtendDto
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static HobbyExtendDto MapperToHobbyExtendDto(this HobbyExtend model)
      {
            if (model == null)
                return null;
            var retn = new HobbyExtendDto()
            {
                Extend1 = model.Extend1,
                Extend2 = model.Extend2,
                Extend3 = model.Extend3?.MapperToInnerExtendDto(),
            };
            MapperToPartial(model, retn);
            return retn;
      }

      /// &lt;summary&gt;
      /// ProjectTo HobbyExtendDto
      /// &lt;/summary&gt;
      public static IQueryable&lt;HobbyExtendDto&gt; ProjectToHobbyExtendDto(this IQueryable&lt;HobbyExtend&gt; query)
      {
            return query.Select(model =&gt; model.MapperToHobbyExtendDto());
      }
    }

    public static partial class HobbyExtendDtoToHobbyExtendExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(HobbyExtendDto from, HobbyExtend to);
      /// &lt;summary&gt;
      /// mapper to HobbyExtend
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static HobbyExtend MapperToHobbyExtend(this HobbyExtendDto model)
      {
            if (model == null)
                return null;
            var retn = new HobbyExtend()
            {
                Extend1 = model.Extend1,
                Extend2 = model.Extend2,
                Extend3 = model.Extend3?.MapperToInnerExtend(),
            };
            MapperToPartial(model, retn);
            return retn;
      }
    }
}

//generate InnerExtend-InnerExtendDto
namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;

    public partial class InnerExtendDto
    {
      /// &lt;inheritdoc cref = "InnerExtend.InnerExtendMsg"/&gt;
      public string InnerExtendMsg { get; set; }
    }
}

namespace Biwen.AutoClassGen.TestConsole.Dtos
{
    using Biwen.AutoClassGen.TestConsole.Dtos;
    using System.Linq;

    public static partial class InnerExtendToInnerExtendDtoExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(InnerExtend from, InnerExtendDto to);
      /// &lt;summary&gt;
      /// mapper to InnerExtendDto
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static InnerExtendDto MapperToInnerExtendDto(this InnerExtend model)
      {
            if (model == null)
                return null;
            var retn = new InnerExtendDto()
            {
                InnerExtendMsg = model.InnerExtendMsg,
            };
            MapperToPartial(model, retn);
            return retn;
      }

      /// &lt;summary&gt;
      /// ProjectTo InnerExtendDto
      /// &lt;/summary&gt;
      public static IQueryable&lt;InnerExtendDto&gt; ProjectToInnerExtendDto(this IQueryable&lt;InnerExtend&gt; query)
      {
            return query.Select(model =&gt; model.MapperToInnerExtendDto());
      }
    }

    public static partial class InnerExtendDtoToInnerExtendExtentions
    {
      /// &lt;summary&gt;
      /// custom mapper
      /// &lt;/summary&gt;
      static partial void MapperToPartial(InnerExtendDto from, InnerExtend to);
      /// &lt;summary&gt;
      /// mapper to InnerExtend
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static InnerExtend MapperToInnerExtend(this InnerExtendDto model)
      {
            if (model == null)
                return null;
            var retn = new InnerExtend()
            {
                InnerExtendMsg = model.InnerExtendMsg,
            };
            MapperToPartial(model, retn);
            return retn;
      }
    }
}
#pragma warning restore

</code></pre>
<h4 id="最后">最后</h4>
<p>以上代码完整的介绍了最近实现的功能,最后你可以使用我最新发布的nuget包体验:</p>
<pre><code class="language-xml">&lt;ItemGroup&gt;
   &lt;PackageReference Include="Biwen.AutoClassGen.Attributes" Version="1.7.0" /&gt;
   &lt;PackageReference Include="Biwen.AutoClassGen" Version="1.7.0" PrivateAssets="all" /&gt;
&lt;/ItemGroup&gt;
</code></pre>
<p>如果你对完整的实现感兴趣可以移步我的GitHub仓储,欢迎star https://github.com/vipwan/Biwen.AutoClassGen</p>
<p>本文版权归作者所有,转载请注明出处!</p><br><br>
来源:https://www.cnblogs.com/vipwan/p/18815596
頁: [1]
查看完整版本: 经过几天的努力Biwen.AutoClassGen终于实现了DTO复杂属性的生成