Проблема связана не с LinqKit, а с самим выражением, а именно с условным оператором и текущими преобразованиями запросов EF Core 2 и преобразованиями значений.
Проблема в том, что в настоящее время преобразования значений указываются для свойства (столбца), а не для типа. Поэтому для правильного перевода в SQL транслятор должен «вывести» тип константы / параметра из свойства. Это делается для большинства выражений типа, но не для условного оператора.
Итак, первое, что вы должны сделать, это сообщить об этом в систему отслеживания проблем EF Core.
Относительно обходного пути:
К сожалению, функциональность находится внутри класса инфраструктуры, называемого DefaultQuerySqlGenerator
, который наследуется каждым поставщиком базы данных. Служба, предоставляемая этим классом, может быть заменена, хотя и несколько сложным способом, что видно из моего ответа на Ef-Core - какое регулярное выражение я могу использовать для замены имен таблиц на nolock в 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
везде, где вы используете Use{Database}
, например,
.UseSqlServer(...)
.UseCustomSqlServerQuerySqlGenerator()
или
.UseNpgsql(...)
.UseCustomNpgsqlQuerySqlGenerator()
и т.д.
Как только вы это сделаете, перевод (по крайней мере, для SqlServer) будет таким, как ожидалось:
WHERE CASE
WHEN [e].[DueAtDate] < @__now_0
THEN 'Overdue' ELSE [e].[Status]
END = 'Overdue'