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

如何无需手动指定字段和值,使用EF Core的ExecuteUpdateAsync?

自动映射DTO到EF Core ExecuteUpdateAsync的实现

EF Core 7的ExecuteUpdateAsync能直接在数据库层更新数据,但手动逐个调用SetProperty确实繁琐。可以通过反射+表达式树实现类似AutoMapper的自动映射,无需手动指定每个字段。

实现步骤

  1. 编写扩展方法,动态构建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 });
    }
}
  1. 使用方式:
// 忽略主键Id,自动映射DTO中其他匹配的字段
await DataContext.Books
    .Where(x => x.Id == dto.Id)
    .ExecuteUpdateFromDtoAsync(dto, "Id");

说明

  • 自动匹配DTO和实体中同名且可写的属性,跳过值类型的null赋值,避免覆盖数据库默认值。
  • 通过ignoreProperties参数可指定无需更新的字段(比如主键、创建时间等)。
  • 基于表达式树构建更新逻辑,EF Core能正常解析生成优化后的SQL,不会有反射直接赋值的性能问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 21:52:45