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

如何在LINQ2DB中利用表达式批量映射DTO与实体实现更新/插入?

解决方案:基于表达式的LINQ2DB实体与DTO双向映射(CRUD复用)

要实现复用自定义映射表达式完成LINQ2DB的Update/Insert操作,你可以通过扩展方法解析映射表达式,批量生成字段赋值逻辑,同时保留追加自定义字段的能力。以下是具体实现:

1. 核心扩展方法实现

创建一个静态扩展类,封装Update时的批量字段设置,以及Insert时的DTO转实体逻辑:

using System.Linq.Expressions;
using System.Reflection;
using LinqToDB;
using LinqToDB.Data;

public static class Linq2DbMappingExtensions
{
    // 缓存映射关系,避免重复解析表达式的性能开销
    private static readonly Dictionary<(Type EntityType, Type DtoType), List<(PropertyInfo EntityProp, PropertyInfo DtoProp)>> _mappingCache = new();

    /// <summary>
    /// 根据DTO转实体的映射表达式,批量设置Update操作的字段
    /// </summary>
    public static IUpdatable<TEntity> Set<TEntity, TDto>(this IUpdatable<TEntity> updatable, TDto dto, Expression<Func<TDto, TEntity>> mappingExpr)
        where TEntity : class
        where TDto : class
    {
        var cacheKey = (typeof(TEntity), typeof(TDto));
        
        // 优先从缓存读取映射关系
        if (!_mappingCache.TryGetValue(cacheKey, out var propertyMappings))
        {
            // 校验映射表达式格式必须是实体初始化器
            if (mappingExpr.Body is not MemberInitExpression initExpr)
                throw new ArgumentException("映射表达式必须采用 `x => new Entity { Prop = x.Prop }` 的实体初始化格式", nameof(mappingExpr));

            propertyMappings = new List<(PropertyInfo, PropertyInfo)>();
            foreach (var binding in initExpr.Bindings.OfType<MemberAssignment>())
            {
                // 校验赋值右侧必须是DTO的直接属性访问
                if (binding.Expression is not MemberExpression dtoMemberExpr || dtoMemberExpr.Member.DeclaringType != typeof(TDto))
                    throw new InvalidOperationException("映射表达式的属性赋值必须直接引用DTO的属性,不支持复杂转换逻辑");

                var entityProp = typeof(TEntity).GetProperty(binding.Member.Name)!;
                var dtoProp = typeof(TDto).GetProperty(dtoMemberExpr.Member.Name)!;
                propertyMappings.Add((entityProp, dtoProp));
            }

            _mappingCache[cacheKey] = propertyMappings;
        }

        // 遍历映射关系,批量调用Set方法
        foreach (var (entityProp, dtoProp) in propertyMappings)
        {
            var dtoValue = dtoProp.GetValue(dto);
            ParameterExpression param = Expression.Parameter(typeof(TEntity), "e");
            MemberExpression entityPropertyExpr = Expression.Property(param, entityProp);
            LambdaExpression setExpr = Expression.Lambda(Expression.Constant(dtoValue), param);
            
            updatable = updatable.Set((LambdaExpression)setExpr);
        }

        return updatable;
    }

    /// <summary>
    /// 用映射表达式将DTO转换为实体,并执行插入,支持追加自定义字段设置
    /// </summary>
    public static async Task<int> InsertWithMappingAsync<TEntity, TDto>(this ITable<TEntity> table, TDto dto, Expression<Func<TDto, TEntity>> mappingExpr, Action<TEntity>? additionalSetup = null)
        where TEntity : class
        where TDto : class
    {
        // 编译映射表达式,转换DTO为实体
        var mapper = mappingExpr.Compile();
        var entity = mapper(dto);

        // 追加额外字段配置(比如创建时间、操作人等)
        additionalSetup?.Invoke(entity);

        return await table.InsertAsync(entity);
    }
}

2. 使用示例

定义映射表达式

先为DTO和实体编写单向映射表达式(这里是DTO转实体的方向):

// 全局定义或放在DTO/实体类的静态成员中
Expression<Func<SomeDTO, SomeEntityTable>> DtoToEntity =
    x => new SomeEntityTable
    {
        ID = x.ID,
        Name = x.Name,
        Description = x.Description,
        // 其他需要映射的字段
    };

Update操作

复用映射表达式批量设置字段,同时支持追加自定义字段:

using var db = ConnectionFactory.Instance.GetMainDB();
var dto = new SomeDTO { ID = 1, Name = "更新后的名称" };

await db.SomeEntityTable
    .Where(e => e.ID == dto.ID)
    .Set(dto, DtoToEntity) // 批量设置映射表达式中定义的字段
    .Set(e => e.LastEditedAt, DateTime.Now) // 追加额外字段
    .UpdateAsync();

Insert操作

用同一映射表达式转换DTO为实体,同时补充额外字段:

using var db = ConnectionFactory.Instance.GetMainDB();
var newDto = new SomeDTO { Name = "新实体", Description = "测试描述" };

await db.SomeEntityTable
    .InsertWithMappingAsync(newDto, DtoToEntity, entity =>
    {
        entity.CreatedAt = DateTime.Now;
        entity.LastEditedAt = DateTime.Now;
        entity.CreatedBy = "当前用户ID";
    });

注意事项

  • 映射表达式仅支持直接属性赋值,不支持复杂的转换逻辑(比如x => new Entity { Age = x.BirthDate.Year }),如果需要复杂转换,需要扩展解析逻辑处理MethodCallExpression或UnaryExpression。
  • 缓存机制会自动存储映射关系,避免重复解析表达式的性能损耗,适合高频使用场景。
  • 如果实体或DTO的属性结构发生变化,缓存会自动更新(因为缓存键是实体+DTO的类型组合)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 22:15:33