Efcore.pg: Fix type inference for ILikeExpression when escape char isn't specified

Created on 25 Jun 2020  路  8Comments  路  Source: npgsql/efcore.pg

Hi,
With the following table:

CREATE TABLE public."Values"
(
    "Guid" bigint NOT NULL,
    "RawValue" text COLLATE pg_catalog."default",
    CONSTRAINT "Values_pkey" PRIMARY KEY ("Guid")
)

The following sample:
``` C#
public class EFCoreTestContext : DbContext
{
public DbSet Values { get; set; }

    public static readonly ILoggerFactory DebugLoggerFactory = LoggerFactory.Create(builder => { builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole(); });

    [DbFunction("unaccent")]
    public static string Unaccent(string value) => throw new NotSupportedException("Esse m茅todo n茫o deve ser utilizado em memoria. Ele existe somente para ser traduzido para sql.");

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder
        .UseLoggerFactory(DebugLoggerFactory)
        .UseNpgsql("Host=localhost;Database=EFCoreTest;Username=test;Password=test");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<Value>(eb =>
            {
                eb.ToTable("Values");
                eb.HasKey(e => e.Guid);
            });
    }
}

public class Value
{
    public long Guid { get; set; }

    public string RawValue { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        using (var db = new EFCoreTestContext())
        {
            var result0 = (from e in db.Values
                           where EF.Functions.ILike(e.RawValue, "%a%")
                           select e).ToList();
            /* Generates:
             * SELECT v."Guid", v."RawValue"
             * FROM "Values" AS v
             * WHERE v."RawValue" ILIKE '%a%'
             */

            var result1 = (from e in db.Values
                           where EFCoreTestContext.Unaccent(e.RawValue).Contains("a")
                           select e).ToList();
            /* Generates:
             * SELECT v."Guid", v."RawValue"
             * FROM "Values" AS v
             * WHERE STRPOS(unaccent(v."RawValue"), 'a') > 0
             */

            var result2 = (from e in db.Values
                           where EF.Functions.ILike(EFCoreTestContext.Unaccent(e.RawValue), "%a%")
                           select e).ToList();
            /* I hoped for... :
             * SELECT v."Guid", v."RawValue"
             * FROM "Values" AS v
             * WHERE unaccent(v."RawValue") ILIKE '%a%'
             */
        }

        Console.ReadKey();
    }
}
I'm getting this error:

> Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.

I was expecting for this query translated:
``` SQL
SELECT v."Guid", v."RawValue"
FROM "Values" AS v
WHERE unaccent(v."RawValue") ILIKE '%a%'

Thanks in advance,

bug

All 8 comments

This looks like a general, non-Npgsql related error - can you please move this to https://github.com/dotnet/efcore/issues? A full stack trace would be useful too.

Actually before moving this... Your code doesn't trigger the exception for me. I copy-pasted it almost exactly as above, using version 3.1.3 of the provider. Can you please double-check?


Full code

```c#
public class EFCoreTestContext : DbContext
{
public DbSet Values { get; set; }

public static readonly ILoggerFactory DebugLoggerFactory = LoggerFactory.Create(builder => { builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole(); });

[DbFunction("unaccent")]
public static string Unaccent(string value) => throw new NotSupportedException("Esse m茅todo n茫o deve ser utilizado em memoria. Ele existe somente para ser traduzido para sql.");

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder
    .UseLoggerFactory(DebugLoggerFactory)
    .UseNpgsql(@"Host=localhost;Username=test;Password=test");

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Value>(eb =>
        {
            eb.ToTable("Values");
            eb.HasKey(e => e.Guid);
        });
}

}

public class Value
{
public long Guid { get; set; }

public string RawValue { get; set; }

}

public static class Program
{
static void Main(string[] args)
{
using (var db = new EFCoreTestContext())
{
var result0 = (from e in db.Values
where EF.Functions.ILike(e.RawValue, "%a%")
select e).ToList();
/* Generates:
* SELECT v."Guid", v."RawValue"
* FROM "Values" AS v
* WHERE v."RawValue" ILIKE '%a%'
*/

        var result1 = (from e in db.Values
                       where EFCoreTestContext.Unaccent(e.RawValue).Contains("a")
                       select e).ToList();
        /* Generates:
         * SELECT v."Guid", v."RawValue"
         * FROM "Values" AS v
         * WHERE STRPOS(unaccent(v."RawValue"), 'a') > 0
         */

        var result2 = (from e in db.Values
                       where EF.Functions.ILike(EFCoreTestContext.Unaccent(e.RawValue), "%a%")
                       select e).ToList();
        /* I hoped for... :
         * SELECT v."Guid", v."RawValue"
         * FROM "Values" AS v
         * WHERE unaccent(v."RawValue") ILIKE '%a%'
         */
    }

    Console.ReadKey();
}

}
```

Firstly, thank you for your response.
Secondly, sure! Double-checking right now.

Copy-pasted your code (changed just the database connection string) and still got the error:

Error's full stack trace

> System.NullReferenceException: Object reference not set to an instance of an object. > at Microsoft.EntityFrameworkCore.Query.ExpressionExtensions.InferTypeMapping(SqlExpression[] expressions) > at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlSqlExpressionFactory.ApplyTypeMappingOnILike(ILikeExpression ilikeExpression) > at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlSqlExpressionFactory.ApplyTypeMapping(SqlExpression sqlExpression, RelationalTypeMapping typeMapping) > at Microsoft.EntityFrameworkCore.Query.SqlExpressionFactory.ApplyDefaultTypeMapping(SqlExpression sqlExpression) > at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlSqlExpressionFactory.ILike(SqlExpression match, SqlExpression pattern, SqlExpression escapeChar) > at Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal.NpgsqlLikeTranslator.Translate(SqlExpression instance, MethodInfo method, IReadOnlyList`1 arguments) > at Microsoft.EntityFrameworkCore.Query.RelationalMethodCallTranslatorProvider.<>c__DisplayClass4_0.b__1(IMethodCallTranslator t) > at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext() > at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found) > at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate) > at Microsoft.EntityFrameworkCore.Query.RelationalMethodCallTranslatorProvider.Translate(IModel model, SqlExpression instance, MethodInfo method, IReadOnlyList`1 arguments) > at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) > at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlSqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCall) > at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) > at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.Translate(Expression expression) > at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateExpression(Expression expression) > at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateLambdaExpression(ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression) > at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate) > at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) > at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) > at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) > at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query) > at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async) > at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async) > at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.b__0() > at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler) > at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler) > at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query) > at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression) > at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator() > at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) > at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) > at ConApp.UnaccentEF.Program.Main(String[] args) in C:\Users\tanie\source\repos\ConApp.UnaccentEF\ConApp.UnaccentEF\Program.cs:line 131

My packages are:

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.5" />
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
  </ItemGroup>

This did turn out to be a bug in the provider, thanks!

No sr., thank YOU!
Such a quick fix. Thanks a lot. XD

My pleasure. You should be able to work around this for now by specifying an empty string as your escape character in EF.Functions.ILike.

Thanks a lot!

Was this page helpful?
0 / 5 - 0 ratings