如何无需手动指定字段和值,使用EF Core的ExecuteUpdateAsync?
自动映射DTO到EF Core ExecuteUpdateAsync的实现
EF Core 7的ExecuteUpdateAsync能直接在数据库层更新数据,但手动逐个调用SetProperty确实繁琐。可以通过反射+表达式树实现类似AutoMapper的自动映射,无需手动指定每个字段。
实现步骤
- 编写扩展方法,动态构建
ExecuteUpdate的更新表达式:
using System.Linq.Expressions; using System.Reflection; public static class QueryableExtensions { public static Task<int> ExecuteUpdateFromDtoAsync<TEntity, TDto>(this IQueryable<TEntity> query, TDto dto, params string[] ignoreProperties) where TEntity : class where TDto : class { var entityType = typeof(TEntity); var dtoType = typeof(TDto); // 获取DTO的所有可读写属性,排除指定忽略字段 var dtoProperties = dtoType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanRead && !ignoreProperties.Contains(p.Name)) .ToList(); // 构建实体参数表达式 var entityParam = Expression.Parameter(entityType, "z"); var updateBuilderType = typeof(SetPropertyCalls<>).MakeGenericType(entityType); // 初始化更新构建器表达式 var builderParam = Expression.Parameter(updateBuilderType, "x"); Expression currentExpression = builderParam; foreach (var dtoProp in dtoProperties) { // 匹配实体中同名可写属性 var entityProp = entityType.GetProperty(dtoProp.Name); if (entityProp == null || !entityProp.CanWrite) continue; var dtoValue = dtoProp.GetValue(dto); // 跳过值类型的null赋值,避免覆盖数据库默认值 if (dtoValue == null && entityProp.PropertyType.IsValueType && Nullable.GetUnderlyingType(entityProp.PropertyType) == null) continue; // 构建实体属性访问表达式 var entityPropertyAccess = Expression.Property(entityParam, entityProp); // 构建值常量表达式(适配可空类型) var valueExpr = Expression.Constant(dtoValue, entityProp.PropertyType); // 获取SetProperty方法 var setPropertyMethod = updateBuilderType.GetMethod("SetProperty", new[] { typeof(Expression<>).MakeGenericType(entityProp.PropertyType), entityProp.PropertyType }); if (setPropertyMethod == null) continue; // 链式调用SetProperty var setPropertyCall = Expression.Call(currentExpression, setPropertyMethod, Expression.Lambda(entityPropertyAccess, entityParam), valueExpr); currentExpression = setPropertyCall; } // 构建最终的更新lambda表达式 var updateLambda = Expression.Lambda(currentExpression, builderParam); // 反射调用ExecuteUpdateAsync var executeUpdateMethod = typeof(EntityFrameworkQueryableExtensions) .GetMethod("ExecuteUpdateAsync", new[] { typeof(IQueryable<TEntity>), typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(updateBuilderType, updateBuilderType)), typeof(CancellationToken) }) ?.MakeGenericMethod(entityType); if (executeUpdateMethod == null) throw new InvalidOperationException("无法找到ExecuteUpdateAsync方法"); return (Task<int>)executeUpdateMethod.Invoke(null, new object[] { query, updateLambda, CancellationToken.None }); } }
- 使用方式:
// 忽略主键Id,自动映射DTO中其他匹配的字段 await DataContext.Books .Where(x => x.Id == dto.Id) .ExecuteUpdateFromDtoAsync(dto, "Id");
说明
- 自动匹配DTO和实体中同名且可写的属性,跳过值类型的null赋值,避免覆盖数据库默认值。
- 通过
ignoreProperties参数可指定无需更新的字段(比如主键、创建时间等)。 - 基于表达式树构建更新逻辑,EF Core能正常解析生成优化后的SQL,不会有反射直接赋值的性能问题。
内容的提问来源于stack exchange,提问作者Adnan
相关产品推荐
相关产品推荐

