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

如何将EFCore.BulkExtensions的BatchUpdate替换为EF Core原生ExecuteUpdate

实现方案:兼容BulkExtensions写法的EF Core原生批量更新扩展

你可以通过自定义扩展方法模拟EFCore.BulkExtensions的BatchUpdate/BatchUpdateAsync签名,内部自动转换为EF Core 8的ExecuteUpdate/ExecuteUpdateAsync调用,完全不需要修改原有业务代码。

核心思路

解析BatchUpdate传入的对象初始化表达式(如new Item { Quantity = a.Quantity + 100 }),提取每个属性的赋值逻辑,再映射为ExecuteUpdate所需的SetProperty调用。

完整扩展方法代码

using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;

public static class QueryableBatchUpdateExtensions
{
    // 同步版本:匹配BulkExtensions的BatchUpdate签名
    public static int BatchUpdate<T>(this IQueryable<T> source, Expression<Func<T, T>> updateExpression)
        where T : class
    {
        var memberInitExpr = updateExpression.Body as MemberInitExpression;
        if (memberInitExpr == null)
            throw new ArgumentException("仅支持对象初始化表达式,格式示例:x => new T { Prop1 = x.Prop1 + 1 }");

        var parameter = updateExpression.Parameters[0];
        var setterBuilderExpr = Expression.New(typeof(SetPropertyCalls<T>));

        foreach (var binding in memberInitExpr.Bindings.OfType<MemberAssignment>())
        {
            var property = (PropertyInfo)binding.Member;
            var valueExpr = binding.Expression;
            var valueExprWithParam = ParameterReplacer.Replace(valueExpr, updateExpression.Parameters[0], parameter);

            var setPropertyMethod = typeof(SetPropertyCalls<T>)
                .GetMethod(nameof(SetPropertyCalls<T>.SetProperty))!
                .MakeGenericMethod(property.PropertyType);

            setterBuilderExpr = Expression.Call(
                instance: setterBuilderExpr,
                method: setPropertyMethod,
                arg0: Expression.Lambda(Expression.Property(parameter, property), parameter),
                arg1: Expression.Lambda(valueExprWithParam, parameter));
        }

        var executeUpdateMethod = typeof(RelationalQueryableExtensions)
            .GetMethods()
            .First(m => m.Name == nameof(RelationalQueryableExtensions.ExecuteUpdate) && m.GetParameters().Length == 2)
            .MakeGenericMethod(typeof(T));

        var result = executeUpdateMethod.Invoke(
            null,
            new object[] { source, Expression.Lambda<Func<SetPropertyCalls<T>, SetPropertyCalls<T>>>(setterBuilderExpr, parameter) });

        return (int)result!;
    }

    // 异步版本:匹配BulkExtensions的BatchUpdateAsync签名
    public static async Task<int> BatchUpdateAsync<T>(this IQueryable<T> source, Expression<Func<T, T>> updateExpression, CancellationToken cancellationToken = default)
        where T : class
    {
        var memberInitExpr = updateExpression.Body as MemberInitExpression;
        if (memberInitExpr == null)
            throw new ArgumentException("仅支持对象初始化表达式,格式示例:x => new T { Prop1 = x.Prop1 + 1 }");

        var parameter = updateExpression.Parameters[0];
        var setterBuilderExpr = Expression.New(typeof(SetPropertyCalls<T>));

        foreach (var binding in memberInitExpr.Bindings.OfType<MemberAssignment>())
        {
            var property = (PropertyInfo)binding.Member;
            var valueExpr = binding.Expression;
            var valueExprWithParam = ParameterReplacer.Replace(valueExpr, updateExpression.Parameters[0], parameter);

            var setPropertyMethod = typeof(SetPropertyCalls<T>)
                .GetMethod(nameof(SetPropertyCalls<T>.SetProperty))!
                .MakeGenericMethod(property.PropertyType);

            setterBuilderExpr = Expression.Call(
                instance: setterBuilderExpr,
                method: setPropertyMethod,
                arg0: Expression.Lambda(Expression.Property(parameter, property), parameter),
                arg1: Expression.Lambda(valueExprWithParam, parameter));
        }

        var executeUpdateAsyncMethod = typeof(RelationalQueryableExtensions)
            .GetMethods()
            .First(m => m.Name == nameof(RelationalQueryableExtensions.ExecuteUpdateAsync) && m.GetParameters().Length == 3)
            .MakeGenericMethod(typeof(T));

        var task = (Task<int>)executeUpdateAsyncMethod.Invoke(
            null,
            new object[] { source, Expression.Lambda<Func<SetPropertyCalls<T>, SetPropertyCalls<T>>>(setterBuilderExpr, parameter), cancellationToken })!;

        return await task;
    }

    // 替换表达式中的参数,解决参数引用不一致问题
    private class ParameterReplacer : ExpressionVisitor
    {
        private readonly ParameterExpression _oldParam;
        private readonly ParameterExpression _newParam;

        private ParameterReplacer(ParameterExpression oldParam, ParameterExpression newParam)
        {
            _oldParam = oldParam;
            _newParam = newParam;
        }

        public static Expression Replace(Expression expr, ParameterExpression oldParam, ParameterExpression newParam)
        {
            return new ParameterReplacer(oldParam, newParam).Visit(expr);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == _oldParam ? _newParam : base.VisitParameter(node);
        }
    }
}

使用方式

原有代码无需任何修改,直接运行即可:

// 同步批量更新
context.Items.Where(a => a.ItemId <= 500)
             .BatchUpdate(a => new Item { Quantity = a.Quantity + 100 });

// 异步批量更新
await context.Items.Where(a => a.ItemId <= 500)
                   .BatchUpdateAsync(a => new Item { Quantity = a.Quantity + 100 });

注意事项

  • 仅支持顶层属性的简单赋值,不支持嵌套对象初始化(和BulkExtensions的BatchUpdate行为一致)。
  • 依赖EF Core 8及以上版本,需确保项目已升级到对应版本。
  • EF Core原生ExecuteUpdate的限制同样适用(如不支持导航属性更新、依赖数据库支持的SQL语法等)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 06:11:06