我尝试在调用ExecuteUpdate时通过拦截器为所有模型设置包含编辑日期和editUserId的基础模型。但是当我尝试设置用户ID时,程序崩溃并出现以下异常:
valueExpression:无法转换“AppContext.user))”。其他信息:以下“SetProperty”无法转换:'SetProperty(p => p.Author,AppContext.user)'。
这是我的密码
internal class ExecuteUpdateInterceptor : IQueryExpressionInterceptor
{
private List<(Type Type, Delegate Calls, Func<IEntityType, bool> Filter)> items = new();
public ExecuteUpdateInterceptor Add<TSource>(
Func<Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>>> source,
Func<IEntityType, bool> filter = null)
{
items.Add((typeof(TSource), source, filter));
return this;
}
Expression IQueryExpressionInterceptor.QueryCompilationStarting(
Expression queryExpression, QueryExpressionEventData eventData)
{
try
{
if (queryExpression is MethodCallExpression call &&
call.Method.DeclaringType == typeof(RelationalQueryableExtensions) &&
(call.Method.Name == nameof(RelationalQueryableExtensions.ExecuteUpdate) ||
call.Method.Name == nameof(RelationalQueryableExtensions.ExecuteUpdateAsync)))
{
var setPropertyCalls = (LambdaExpression)((UnaryExpression)call.Arguments[1]).Operand;
var body = setPropertyCalls.Body;
var parameter = setPropertyCalls.Parameters[0];
var targetType = eventData.Context?.Model.FindEntityType(parameter.Type.GetGenericArguments()[0]);
if (targetType != null)
{
foreach (var item in items)
{
if (!item.Type.IsAssignableFrom(targetType.ClrType))
continue;
if (item.Filter != null && !item.Filter(targetType))
continue;
var calls = (LambdaExpression)item.Calls.Method.GetGenericMethodDefinition()
.MakeGenericMethod(targetType.ClrType)
.Invoke(null, null);
body = calls.Body.ReplaceParameter(calls.Parameters[0], body);
}
if (body != setPropertyCalls.Body)
{
//body
return call.Update(call.Object, new[] { call.Arguments[0], Expression.Lambda(body, parameter) });
}
}
}
return queryExpression;
}
catch (Exception ex)
{
throw;
}
}
}
和/或
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(new ExecuteUpdateInterceptor()
.Add(SetBaseProperty<BassyBase>));
}
public static Guid? user = new Guid("D4DB8C0E-E21B-4456-8974-22B2FFE0F2C1");
private static Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> SetBaseProperty<TSource>()
where TSource : BassyBase
{
Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> props = s => s
.SetProperty(p => p.ChangeDate, DateTimeOffset.UtcNow)
.SetProperty(p => p.Author, user);
return props;
}
如果我试着用SetProperty(p => p.Author, Guid.NewGuid() or new Guid("XXX") )
将其设置为内联,它就可以工作了。
但是当我试图从变量中获取字符串或guid时,它似乎试图发送变量的引用,而不是导致查询崩溃的值。
2条答案
按热度按时间s1ag04yj1#
相当烦人的EF核心bug/限制。很长一段时间,我们(为表达式树操作编写扩展/解决方案的人)要求在查询翻译管道中提供扩展点,以便我们可以接收原始表达式树并在EF Core之前进行预处理/转换。我们最终得到了它,但事实证明,在典型的EF Core方式中,经过一些EF Core预处理,特别是查询的 * 参数化 *。
最后一个是非常重要的细节,因为它限制了您在拦截器中可以做的事情。通常使用捕获的变量或静态属性/字段/方法访问器的表达式如下所示(注意,我们在表达式树中,因此没有真实的的调用)
被转换为(替换为)参数。但是由于拦截器是在参数化之后调用的,因此它们不会转换为参数,并且稍后会报告为未知/不支持的表达式是表达式树中的任何自定义方法调用。
Shorty,现在的方式是,你不能在结果表达式树中引入非常量表达式。为了使事情变得更难,C#没有提供从 * 变量 * 创建常量表达式的语法(即而不是使用
const
字段)。所以,首先,最好在EF Core GitHub问题跟踪器中将此报告为错误并请求修复(人们会去投票)。其次,我看到的使用拦截器的唯一解决方法是将变量转换为常量。这可以通过手动构建表达式树来完成,或者像往常一样,通过创建带有附加参数“标记”的原型表达式,然后用常量值替换它们。
在你的例子中,它可能是这样的:
k2arahey2#
您面临的问题与用于设置SetBaseProperty方法中Author属性的表达式的转换有关。当您使用变量(AppContext.user)而不是常量值时,查询转换将失败,因为它试图发送对变量的引用而不是其值。
若要解决此问题,可以通过在SetBaseProperty方法中创建局部变量来修改代码,并使用该变量设置Author属性。下面是代码的更新版本:
通过创建局部变量author并将用户的值赋给它,可以确保查询转换将使用变量的值,而不是将其视为引用。这应该可以解决问题,并允许您使用AppContext.user值设置Author属性。