Проблема связана не с LinqKit, а с самим выражением, в частности с условным оператором и текущим преобразованием запросов и значений EF Core 2.
Проблема в том, что в настоящее время преобразования значений указываются для каждого свойства (столбца), а не для каждого типа. Таким образом, для корректного перевода в SQL транслятор должен «выводить» тип константы/параметра из свойства. Он делает это для большинства типов выражений, но не для условного оператора.
Итак, первое, что вы должны сделать, — это сообщить об этом в средство отслеживания проблем EF Core.
Что касается обходного пути:
К сожалению, эта функциональность находится внутри класса инфраструктуры с именем DefaultQuerySqlGenerator
, который наследуется каждым поставщиком базы данных. Услуга, предоставляемая этим классом, может быть заменена, хотя и несколько сложным способом, что можно увидеть в моем ответе на Ef-Core - Какое регулярное выражение я могу использовать для замены имен таблиц без блокировки в 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()
or
.UseNpgsql(...)
.UseCustomNpgsqlQuerySqlGenerator()
и т.п.
Как только вы это сделаете, перевод (по крайней мере, для SqlServer) будет таким, как ожидалось:
WHERE CASE
WHEN [e].[DueAtDate] < @__now_0
THEN 'Overdue' ELSE [e].[Status]
END = 'Overdue'
person
Ivan Stoev
schedule
20.03.2019
MyEnum.Overdue.ToSring()
- person TanvirArjel   schedule 17.03.2019CASE WHEN ….THEN 2 ELSE e.status END = 2
, т.е. все еще неправильный перевод, но обе константы enum не преобразуются в строки. Какую именно версию EF Core вы используете? - person Ivan Stoev   schedule 17.03.2019toString()
для перечислений, было бы очень неудобно, так как на этом этапе мне пришлось бы вызывать его для всех из них. Кроме того,toString()
не транслируется в SQL, поэтому операция выполняется в памяти. - person Francesco D.M.   schedule 18.03.2019? :
) - они пропустили этот случай. - person Ivan Stoev   schedule 19.03.2019