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

EF Core 10中适配ExecuteUpdate动态属性设置方法的方案咨询

适配EF Core 10的ExecuteUpdateAsync动态扩展方法

问题背景

Microsoft在.NET 10(EF Core 10)中修改了ExecuteUpdateAsync方法,使其接受基于UpdateSettersBuilder<TEntity>的非表达式设置器参数,当前典型用法如下:

await context.Blogs.ExecuteUpdateAsync(s =>
{
    s.SetProperty(b => b.Views, 8);

    if (nameChanged)
    {
        s.SetProperty(b => b.Name, "foo");
    }
});

原有针对未知实体、支持单个/多个属性更新的扩展方法基于旧版SetPropertyCalls<TEntity>实现,现在需要适配到新的UpdateSettersBuilder<TEntity>类型。用户已定位到替换类型,但不清楚后续表达式构建的调整方式。

原有单个属性更新扩展方法

public static Task<int> ExecuteUpdateAsync(this IQueryable Source, Type EntityType, string FieldName, object? FieldValue, CancellationToken cancellationToken = default)
{
    MethodInfo? UpdateAsyncMethodInfo = typeof(EntityFrameworkQueryableExtensions).GetMethod(nameof(EntityFrameworkQueryableExtensions.ExecuteUpdateAsync)) ??
                throw new Exception("EntityFrameworkQueryableExtensions.ExecuteUpdateAsync method is not found");
    Type SetPropertyCallsEntity = typeof(SetPropertyCalls<>).MakeGenericType(EntityType);

    ParameterExpression EntityParam = Expression.Parameter(SetPropertyCallsEntity, "r");
    ParameterExpression PropertyParam = Expression.Parameter(EntityType, "p");

    MemberExpression PropertyExp = Expression.PropertyOrField(PropertyParam, FieldName);
    Expression ValueExp = FieldValue == null ? Expression.Constant(null, PropertyExp.Type) : Expression.Convert(Expression.Constant(FieldValue), PropertyExp.Type);
    // r.SetProperty(p => p.SomeField, value)
    MethodInfo? SetPropertyMethodInfo = (SetPropertyCallsEntity.GetMethods()
                .FirstOrDefault(r => r.Name == nameof(SetPropertyCalls<object>.SetProperty) && r.GetParameters().Any(p => p.ParameterType.BaseType == typeof(object)))?
                .MakeGenericMethod(PropertyExp.Type)) ?? throw new Exception("SetPropertyCalls<>.SetProperty method is not found");
    MethodCallExpression SetBody = Expression.Call(EntityParam, SetPropertyMethodInfo, Expression.Lambda(PropertyExp, PropertyParam), ValueExp);

    LambdaExpression UpdateBody = Expression.Lambda(SetBody, EntityParam);

    return (Task<int>)UpdateAsyncMethodInfo.MakeGenericMethod(Source.ElementType).Invoke(null, [Source, UpdateBody, cancellationToken])!;
}

原有多个属性更新扩展方法

public static Task<int> ExecuteUpdateAsync(this IQueryable Source, Type EntityType, Dictionary<string, object?> FieldValues, CancellationToken cancellationToken = default)
{
    MethodInfo? UpdateAsyncMethodInfo = typeof(EntityFrameworkQueryableExtensions).GetMethod(nameof(EntityFrameworkQueryableExtensions.ExecuteUpdateAsync)) ??
             throw new Exception("EntityFrameworkQueryableExtensions.ExecuteUpdateAsync method is not found");
    Type SetPropertyCallsEntity = typeof(SetPropertyCalls<>).MakeGenericType(EntityType);

    ParameterExpression EntityParam = Expression.Parameter(SetPropertyCallsEntity, "r");
    ParameterExpression PropertyParam = Expression.Parameter(EntityType, "p");
    Expression SetBody = EntityParam;

    foreach (KeyValuePair<string, object?> FieldValue in FieldValues)
    {
        MemberExpression PropertyExp = Expression.PropertyOrField(PropertyParam, FieldValue.Key);
        Expression ValueExp = FieldValue.Value == null ? Expression.Constant(null, PropertyExp.Type) : Expression.Convert(Expression.Constant(FieldValue.Value), PropertyExp.Type);
        // r.SetProperty(p => p.SomeField, value).SetProperty(p => p.AnotherField, AnotherValue)
        MethodInfo? SetPropertyMethodInfo = (SetPropertyCallsEntity.GetMethods()
                    .FirstOrDefault(r => r.Name == nameof(SetPropertyCalls<object>.SetProperty) && r.GetParameters().Any(p => p.ParameterType.BaseType == typeof(object)))?
                    .MakeGenericMethod(PropertyExp.Type)) ?? throw new Exception("SetPropertyCalls<>.SetProperty method is not found");
        SetBody = Expression.Call(SetBody, SetPropertyMethodInfo, Expression.Lambda(PropertyExp, PropertyParam), ValueExp);
    }

    LambdaExpression UpdateBody = Expression.Lambda(SetBody, EntityParam);

    return (Task<int>)UpdateAsyncMethodInfo.MakeGenericMethod(Source.ElementType).Invoke(null, [Source, UpdateBody, cancellationToken])!;
}

适配EF Core 10的解决方案

核心改动是将SetPropertyCalls<TEntity>替换为UpdateSettersBuilder<TEntity>,同时调整SetProperty方法的匹配逻辑、表达式构建方式(因新版SetProperty返回void,不再支持链式调用),以及Lambda表达式的返回值处理。

适配后的单个属性更新扩展方法

public static Task<int> ExecuteUpdateAsync(this IQueryable Source, Type EntityType, string FieldName, object? FieldValue, CancellationToken cancellationToken = default)
{
    // 获取ExecuteUpdateAsync的泛型方法定义
    var updateAsyncMethod = typeof(EntityFrameworkQueryableExtensions)
        .GetMethods()
        .First(m => m.Name == nameof(EntityFrameworkQueryableExtensions.ExecuteUpdateAsync) && 
                    m.GetParameters().Length == 3 &&
                    m.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Action<>))
        ?? throw new InvalidOperationException("ExecuteUpdateAsync method not found");

    var setterBuilderType = typeof(UpdateSettersBuilder<>).MakeGenericType(EntityType);

    // 构建参数表达式:setterBuilder参数
    var setterBuilderParam = Expression.Parameter(setterBuilderType, "s");
    // 构建实体参数:用于属性访问表达式
    var entityParam = Expression.Parameter(EntityType, "e");

    // 构建属性访问表达式
    var propertyExp = Expression.PropertyOrField(entityParam, FieldName);
    // 构建值表达式(处理null和类型转换)
    var valueExp = FieldValue == null 
        ? Expression.Constant(null, propertyExp.Type) 
        : Expression.Convert(Expression.Constant(FieldValue), propertyExp.Type);

    // 获取SetProperty方法:匹配接受属性选择器和常量值的重载
    var setPropertyMethod = setterBuilderType.GetMethods()
        .First(m => m.Name == nameof(UpdateSettersBuilder<object>.SetProperty) && 
                    m.GetParameters().Length == 2 &&
                    m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Expression<Func<,>>))
        .MakeGenericMethod(propertyExp.Type);

    // 构建SetProperty调用表达式:s.SetProperty(e => e.FieldName, value)
    var setPropertyCall = Expression.Call(
        setterBuilderParam, 
        setPropertyMethod, 
        Expression.Lambda(propertyExp, entityParam), 
        valueExp);

    // 构建Lambda表达式:Action<UpdateSettersBuilder<TEntity>>
    var updateLambda = Expression.Lambda(setPropertyCall, setterBuilderParam);

    // 调用泛型ExecuteUpdateAsync方法
    var genericUpdateMethod = updateAsyncMethod.MakeGenericMethod(Source.ElementType);
    return (Task<int>)genericUpdateMethod.Invoke(null, new object[] { Source, updateLambda, cancellationToken })!;
}

适配后的多个属性更新扩展方法

public static Task<int> ExecuteUpdateAsync(this IQueryable Source, Type EntityType, Dictionary<string, object?> FieldValues, CancellationToken cancellationToken = default)
{
    // 获取ExecuteUpdateAsync的泛型方法定义
    var updateAsyncMethod = typeof(EntityFrameworkQueryableExtensions)
        .GetMethods()
        .First(m => m.Name == nameof(EntityFrameworkQueryableExtensions.ExecuteUpdateAsync) && 
                    m.GetParameters().Length == 3 &&
                    m.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Action<>))
        ?? throw new InvalidOperationException("ExecuteUpdateAsync method not found");

    var setterBuilderType = typeof(UpdateSettersBuilder<>).MakeGenericType(EntityType);

    // 构建参数表达式:setterBuilder参数
    var setterBuilderParam = Expression.Parameter(setterBuilderType, "s");
    // 构建实体参数:用于属性访问表达式
    var entityParam = Expression.Parameter(EntityType, "e");

    // 收集所有SetProperty调用表达式
    var setPropertyCalls = new List<Expression>();
    foreach (var kvp in FieldValues)
    {
        var propertyExp = Expression.PropertyOrField(entityParam, kvp.Key);
        var valueExp = kvp.Value == null 
            ? Expression.Constant(null, propertyExp.Type) 
            : Expression.Convert(Expression.Constant(kvp.Value), propertyExp.Type);

        var setPropertyMethod = setterBuilderType.GetMethods()
            .First(m => m.Name == nameof(UpdateSettersBuilder<object>.SetProperty) && 
                        m.GetParameters().Length == 2 &&
                        m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Expression<Func<,>>))
            .MakeGenericMethod(propertyExp.Type);

        var setPropertyCall = Expression.Call(
            setterBuilderParam, 
            setPropertyMethod, 
            Expression.Lambda(propertyExp, entityParam), 
            valueExp);

        setPropertyCalls.Add(setPropertyCall);
    }

    // 构建Block表达式包裹多个无返回值的SetProperty调用
    var block = Expression.Block(setPropertyCalls);
    // 构建Lambda表达式:Action<UpdateSettersBuilder<TEntity>>
    var updateLambda = Expression.Lambda(block, setterBuilderParam);

    // 调用泛型ExecuteUpdateAsync方法
    var genericUpdateMethod = updateAsyncMethod.MakeGenericMethod(Source.ElementType);
    return (Task<int>)genericUpdateMethod.Invoke(null, new object[] { Source, updateLambda, cancellationToken })!;
}

关键改动说明

  • 类型替换:用UpdateSettersBuilder<TEntity>完全替换旧的SetPropertyCalls<TEntity>
  • 方法匹配逻辑调整:UpdateSettersBuilder的SetProperty方法返回void,不再支持链式调用,多属性更新时需用Expression.Block包裹所有调用
  • ExecuteUpdateAsync方法匹配:新版方法接受Action<UpdateSettersBuilder<TEntity>>类型委托,Lambda表达式返回类型为void
  • 表达式构建调整:单个属性更新直接用调用表达式作为Lambda体,多属性更新则通过Expression.Block组合多个调用

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 01:20:54