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

