如何在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
相关产品推荐
相关产品推荐

