c# – EFCore枚举到where子句中未使用的字符串值转换
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了c# – EFCore枚举到where子句中未使用的字符串值转换,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含7445字,纯文字阅读大概需要11分钟。
内容图文
![c# – EFCore枚举到where子句中未使用的字符串值转换](/upload/InfoBanner/zyjiaocheng/790/3df907ec6af34d028c6d92054a58b8c9.jpg)
我有一个问题,我的Linq where子句如何被转换为Sql.
我正在使用EnumToStringConverter将我的实体的属性(枚举)映射到文本数据库列.只需从DbContext查询我的实体,这一切都正常.
然后我开始使用LinqKit和Expressions来获得可重用的过滤器.我创建了一个接受我的实体的Expression,并根据对实体其他属性的一些计算给出了我的枚举.我会尝试用代码解释自己,因为单词让我失望.
我会写一个例子,所以我不必发布完整的代码,但逻辑将是相同的.您可以在此处找到包含项目的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正确翻译)与一个表达式进行比较,该表达式在为true时提供int(2是MyEnum.Overdue情况的值)和字符串(e.status)什么时候假.这显然是无效的SQL.
我真的不知道如何解决这个问题.有帮助吗?
解决方法:
该问题与LinqKit无关,而是表达式本身,特别是条件运算符和当前EF Core 2查询转换和值转换.
问题是当前的值转换是按属性(列)而不是按类型指定的.因此,为了正确地转换为SQL,转换器必须从属性“推断”常量/参数类型.它适用于大多数类型的表达式,但不适用于条件运算符.
因此,您应该做的第一件事是将其报告给EF Core问题跟踪器.
关于变通方法:
不幸的是,该功能位于名为DefaultQuerySqlGenerator的基础结构类中,该类由每个数据库提供程序继承.该类提供的服务可以替换,虽然有点复杂,可以在我对Ef-Core – What regex can I use to replace table names with nolock ones in Db Interceptor的回答中看到,并且还必须为您想要支持的每个数据库提供程序完成.
对于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使用{Database},例如
.UseSqlServer(...)
.UseCustomSqlServerQuerySqlGenerator()
要么
.UseNpgsql(...)
.UseCustomNpgsqlQuerySqlGenerator()
等等
一旦你这样做,翻译(至少对于SqlServer)是预期的:
WHERE CASE
WHEN [e].[DueAtDate] < @__now_0
THEN 'Overdue' ELSE [e].[Status]
END = 'Overdue'
内容总结
以上是互联网集市为您收集整理的c# – EFCore枚举到where子句中未使用的字符串值转换全部内容,希望文章能够帮你解决c# – EFCore枚举到where子句中未使用的字符串值转换所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。