EFCore枚举到字符串值的转换未在where子句中使用

弗朗切斯科DM

我如何将Linq where子句转换为Sql时遇到问题。

EnumToStringConverter用来将实体的属性映射enum到文本db列中。仅从DbContext查询我的实体时,这一切都很好。

然后,我开始使用LinqKit和Expressions具有可重用的过滤器。我创建了一个表达式,该表达式接受我的实体并通过对该实体其他属性的一些计算得出我的枚举。我会尝试用代码来解释自己,因为单词使我不满意。我将写一个示例,这样我就不必发布完整的代码,但是逻辑将是相同的。您可以找到一个带有项目的GitHub存储库,以在此处复制问题:https : //github.com/pinoy4/efcore-enum-to-string-test

模型类:

public class MyEntity
{
    public Guid Id { get; set; }
    public MyEnum Status { get; set; }
    public DateTime DueAtDate { get; set; }
}

public MyEnum
{
    New = 0,
    InProgress = 1,
    Overdue = 2
}

FluentAPI配置

public class MyEntityConfiguration : IEntityTypeConfiguration<MyEntity>
{
    public void Configure(EntityTypeBuilder<MyEntity> builder)
    {
        // irrelevant parts of configuration skipped here

        builder.Property(e => e.Status)
            .HasColumnName("status")
            .IsRequired()
            .HasConversion(new EnumToStringConverter<MyEnum>());
    }
}

Linq表达式是使用静态方法生成的。有两个:

public static class MyExpressions
{
    public static Expression<Func<MyEntity, MyEnum>> CalculateStatus(DateTime now)
    {
        /*
         * This is the tricky part as in one case I am returning
         * an enum value that am am setting here and in the other
         * case it is an enum value that is taken from the entity.
         */
        return e => e.DueAtDate < now ? MyEnum.Overdue : e.Status;
    }

    public static Expression<Func<MyEntity, bool>> GetOverdue(DateTime now)
    {
        var calculatedStatus = CalculateStatus(now);
        return e => calculatedStatus.Invoke(e) == MyEnum.Overdue;
    }
}

现在我们有了上面的代码,我这样编写查询:

var getOverdueFilter = MyExpressions.GetOverdue(DateTime.UtcNow);
DbContext.MyEntities.AsExpandable().Where(getOverdueFilter).ToList();

它将转换为以下SQL:

SELECT ... WHERE CASE
  WHEN e.due_at_date < $2 /* the date that we are passing as a parameter */
  THEN 2 ELSE e.status
END = 2;

问题在于该CASE语句正在比较'Overdue'(使用正确转换语句EnumToStringConverter)与一个int为true的表达式(为MyEnum.Overdue情况的值是2)和为stringfalse的表达式(e.status)。这显然是无效的SQL。

我真的不知道该如何解决。有什么帮助吗?

伊万·斯托夫

问题与LinqKit无关,而是表达式本身,特别是条件运算符以及当前的EF Core 2查询转换和值转换。

问题在于,当前值转换是按属性(列)而不是按类型指定的。因此,为了正确地转换为SQL,转换器必须从属性“推断”常量/参数类型。它适用于大多数类型的表达式,但不适用于条件运算符。

因此,您应该做的第一件事就是将其报告给EF Core问题跟踪器。

关于解决方法:

不幸的是,该功能位于名为的基础结构类中DefaultQuerySqlGenerator,该基础结构类由每个数据库提供程序继承。可以替换该类提供的服务,尽管有点复杂,这可以从我对Ef-Core的回答中看出-我可以使用什么正则表达式在Db Interceptor中用nolock替换表名,并且还必须为您要支持的每个数据库提供程序完成。

对于SqlServer,它需要如下所示(经过测试):

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.Sql;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Query.Sql.Internal;

namespace Microsoft.EntityFrameworkCore
{
    public static partial class CustomDbContextOptionsBuilderExtensions
    {
        public static DbContextOptionsBuilder UseCustomSqlServerQuerySqlGenerator(this DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.ReplaceService<IQuerySqlGeneratorFactory, CustomSqlServerQuerySqlGeneratorFactory>();
            return optionsBuilder;
        }
    }
}

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Sql.Internal
{
    class CustomSqlServerQuerySqlGeneratorFactory : SqlServerQuerySqlGeneratorFactory
    {
        private readonly ISqlServerOptions sqlServerOptions;
        public CustomSqlServerQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies, ISqlServerOptions sqlServerOptions)
            : base(dependencies, sqlServerOptions) => this.sqlServerOptions = sqlServerOptions;
        public override IQuerySqlGenerator CreateDefault(SelectExpression selectExpression) =>
            new CustomSqlServerQuerySqlGenerator(Dependencies, selectExpression, sqlServerOptions.RowNumberPagingEnabled);
    }

    public class CustomSqlServerQuerySqlGenerator : SqlServerQuerySqlGenerator
    {
        public CustomSqlServerQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies, SelectExpression selectExpression, bool rowNumberPagingEnabled)
            : base(dependencies, selectExpression, rowNumberPagingEnabled) { }
        protected override RelationalTypeMapping InferTypeMappingFromColumn(Expression expression)
        {
            if (expression is UnaryExpression unaryExpression)
                return InferTypeMappingFromColumn(unaryExpression.Operand);
            if (expression is ConditionalExpression conditionalExpression)
                return InferTypeMappingFromColumn(conditionalExpression.IfTrue) ?? InferTypeMappingFromColumn(conditionalExpression.IfFalse);
            return base.InferTypeMappingFromColumn(expression);
        }
    }
}

对于PostgreSQL(未测试):

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.Sql;
using Microsoft.EntityFrameworkCore.Storage;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Sql.Internal;

namespace Microsoft.EntityFrameworkCore
{
    public static partial class CustomDbContextOptionsBuilderExtensions
    {
        public static DbContextOptionsBuilder UseCustomNpgsqlQuerySqlGenerator(this DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.ReplaceService<IQuerySqlGeneratorFactory, CustomNpgsqlQuerySqlGeneratorFactory>();
            return optionsBuilder;
        }
    }
}

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Sql.Internal
{
    class CustomNpgsqlQuerySqlGeneratorFactory : NpgsqlQuerySqlGeneratorFactory
    {
        private readonly INpgsqlOptions npgsqlOptions;
        public CustomNpgsqlQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies, INpgsqlOptions npgsqlOptions)
            : base(dependencies, npgsqlOptions) => this.npgsqlOptions = npgsqlOptions;
        public override IQuerySqlGenerator CreateDefault(SelectExpression selectExpression) =>
            new CustomNpgsqlQuerySqlGenerator(Dependencies, selectExpression, npgsqlOptions.ReverseNullOrderingEnabled);
    }

    public class CustomNpgsqlQuerySqlGenerator : NpgsqlQuerySqlGenerator
    {
        public CustomNpgsqlQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies, SelectExpression selectExpression, bool reverseNullOrderingEnabled)
            : base(dependencies, selectExpression, reverseNullOrderingEnabled) { }
        protected override RelationalTypeMapping InferTypeMappingFromColumn(Expression expression)
        {
            if (expression is UnaryExpression unaryExpression)
                return InferTypeMappingFromColumn(unaryExpression.Operand);
            if (expression is ConditionalExpression conditionalExpression)
                return InferTypeMappingFromColumn(conditionalExpression.IfTrue) ?? InferTypeMappingFromColumn(conditionalExpression.IfFalse);
            return base.InferTypeMappingFromColumn(expression);
        }
    }
}

除了样板代码,解决方法是

if (expression is UnaryExpression unaryExpression)
    return InferTypeMappingFromColumn(unaryExpression.Operand);
if (expression is ConditionalExpression conditionalExpression)
    return InferTypeMappingFromColumn(conditionalExpression.IfTrue) ?? InferTypeMappingFromColumn(conditionalExpression.IfFalse);

内部InferTypeMappingFromColumn方法重写。

为了产生效果,您需要添加UseCustom{Database}QuerySqlGenerator您使用的任何地方Use{Database},例如

.UseSqlServer(...)
.UseCustomSqlServerQuerySqlGenerator()

要么

.UseNpgsql(...)
.UseCustomNpgsqlQuerySqlGenerator()

等等

完成此操作后,翻译(至少对于SqlServer)将按预期进行:

WHERE CASE
    WHEN [e].[DueAtDate] < @__now_0
    THEN 'Overdue' ELSE [e].[Status]
END = 'Overdue'

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

在WHERE子句中使用字符串值作为布尔值

将字符串转换为数组并在 where 子句中使用

在WHERE子句中将字符串数组转换为自定义枚举类型

在WHERE子句中使用枚举的FlexibleSearch

Laravel在where子句中获取包含字符串的值

在where子句中使用子字符串函数时处理NULL

如何在querybuilder where子句中使用子字符串

在 where 子句中使用字符串比较的 Python MySQL 查询

在Linq where子句中使用带有字符串的switch-case

Linq查询在where子句中使用字符串数组

获取子字符串并在 Where 子句中使用它

在WHERE子句中使用一串值-MySQL

使用 Linq 的 Where 子句中的子字符串

SQL:我可以将“<”运算符的字符串值转换为 where 子句中的运算符吗?

在 Scala 中使用日期到字符串转换的字符串插值

在 where 子句中使用表值

在WHERE子句中使用Count值

SQL Server 无法在 where 子句中将字符串转换为日期(代码 241)

Rails:在where子句中将字符串字段转换为整数

WHERE子句中的EF硬编码值快,字符串参数慢

where子句中的最佳搜索字符串

在SQL的where子句中附加字符串

在where子句中的DataTable.Select字符串函数

Where子句中的PHP变量字符串为空

将另一个表中的逐行值转换为字符串并将其插入到 SQL 中的 NOT IN 子句中

Android-为什么不能在if子句中使用getResources()。getStringArray返回的字符串?

在if语句中使用字符串作为变量子句

在AWS Athena表的group by子句中使用子字符串函数

在knex js(postgres)的SELECT子句中使用字符串