【问题标题】:InvalidCastException thrown when try to get SQL in EF Core尝试在 EF Core 中获取 SQL 时抛出 InvalidCastException
【发布时间】:2020-06-05 01:39:06
【问题描述】:

我尝试使用 x => set.Contains(x.attr) 等集合中的过滤器构建动态表达式,并在 EF Core 3.1 中使用下面的 .ToSQL() 方法获取生成的 SQL。

但它会抛出一个InvalidCastException

异常:System.InvalidCastException:无法转换类型为“System.Collections.Generic.List1[System.Int32]' to type 'System.Collections.Generic.IEnumerable1[System.Object]”的对象。

在 Microsoft.EntityFrameworkCore.Query.QuerySqlGenerator.VisitIn(InExpression inExpression)
在 Microsoft.EntityFrameworkCore.Query.SqlExpressionVisitor.VisitExtension(Expression extensionExpression)
在 System.Linq.Expressions.Expression.Accept(ExpressionVisitor 访问者)
在 Microsoft.EntityFrameworkCore.Query.QuerySqlGenerator.VisitSqlBinary(SqlBinaryExpression sqlBinaryExpression)
在 Microsoft.EntityFrameworkCore.Query.SqlExpressionVisitor.VisitExtension(Expression extensionExpression)
在 System.Linq.Expressions.Expression.Accept(ExpressionVisitor 访问者)
在 Microsoft.EntityFrameworkCore.Query.QuerySqlGenerator.VisitSelect(SelectExpression selectExpression)
在 Microsoft.EntityFrameworkCore.Query.QuerySqlGenerator.GetCommand(SelectExpression selectExpression)
在 Microsoft.EcoManager.Domain.Core.QueryableExtensions.ToSql[TEntity](IQueryable`1 查询)

这是我构建过滤器集动态表达式的代码:

private static Expression<Func<TData, bool>> CreateSetFilterExpression<TData, TProperty>(string property, IEnumerable<TProperty> values)
{
    var type = typeof(TData);
    var arg = Expression.Parameter(type, "x");

    var propertyInfo = type.GetProperty(property);
    Expression exp = Expression.Property(arg, propertyInfo);
    exp = Expression.Convert(exp, typeof(TProperty));

    var methodInfo = typeof(Enumerable)
                .GetMethods()
                .Single(x => x.Name == nameof(Enumerable.Contains) && 
                             x.IsGenericMethodDefinition && 
                             x.GetGenericArguments().Length == 1 && 
                             x.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(TProperty));
    var valuesExpr = Expression.Constant(values);

    exp = Expression.Call(null, methodInfo, valuesExpr, exp);

    var resultLambda = Expression.Lambda<Func<TData, bool>>(exp, arg);
    return resultLambda;
}

似乎与EF Core中QuerySqlGeneratorVisitIn方法有关: https://github.com/dotnet/efcore/blob/2e8ef3516d2bed2f934eea6e2cb92f7a9ff40ab3/src/EFCore.Relational/Query/QuerySqlGenerator.cs#L624

例如,当我尝试使用CreateSetFilterExpression&lt;SomeEntity, int&gt;(SomePropertyName, values) 构造过滤器集动态表达式时,值的类型是List&lt;int&gt;。执行到sqlGenerator.GetCommand in ToSQL() 行时会抛出InvalidCastException

异常:System.InvalidCastException:无法转换类型为“System.Collections.Generic.List1[System.Int32]' to type 'System.Collections.Generic.IEnumerable1[System.Object]”的对象。

在 Microsoft.EntityFrameworkCore.Query.QuerySqlGenerator.VisitIn(InExpression inExpression)
...

我搜索了 int cast to object,并检查了 C# 中的装箱和拆箱概念,但我仍然不知道如何解决它。

有人遇到过同样的问题吗?

如果有关于这个问题的任何线索,请帮助我?

这里是.ToSql()方法的代码:

public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
    var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
    var relationalCommandCache = enumerator.Private("_relationalCommandCache");
    var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
    var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");

    var sqlGenerator = factory.Create();
    var command = sqlGenerator.GetCommand(selectExpression);

    string sql = command.CommandText;
    return sql;
}

private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);

private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);

【问题讨论】:

    标签: c# sql-server ef-core-3.1


    【解决方案1】:

    看起来 TData 类的属性具有 Int32 类型,但 CreateSetFilterExpression() 接收值作为 IEnumerable(和 typeof(TProperty) == typeof(object)),EF 无法将该表达式转换为 SQL:

      exp = Expression.Convert(exp, typeof(TProperty));
    

    尝试将值作为 Int32 列表传递给 CreateSetFilterExpression()。

    【讨论】:

    • 将值传递为 List 引发了我在帖子中提到的异常:无法将类型为 'System.Collections.Generic.List1[System.Int32]' 的对象转换为类型 'System. Collections.Generic.IEnumerable1[System.Object]'。
    • 那么TData属性的类型是什么?
    • 这似乎是 QuerySqlGeneratorFactory 中的一个错误: - 如果在数据库上运行查询(不转换为 SQL),它可以正常工作。 - 如果属性为字符串类型,则 SQL 生成正确
    • 我认为你是对的。如果我尽量不让生成的 SQL 出来,它似乎工作得很好。
    • TData 属性只是一个实体 (SomeEntity),在此示例中它有一个名为 SomePropertyName 的成员:property.CreateSetFilterExpression(SomePropertyName, values)
    猜你喜欢
    • 2021-12-05
    • 2016-11-30
    • 2020-05-26
    • 1970-01-01
    • 2020-02-09
    • 2019-07-01
    • 2021-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多