Traduzindo método C# em SQL query no EF ...

Traduzindo método C# em SQL query no EF Core

Jan 19, 2024

Cenário: A entidade tinha uma propriedade do tipo byte[] que representa uma coluna do tipo timestamp no SQL Server.

image

Necessidade: Buscar todos os registros com uma versão maior ou igual a que foi passada pelo usuário.

Problema: Não é possível fazer operação de Greater Than ou Greater Than Or Equals em cima de byte[], logo, não compila.

image

Solução:

public static class MyDbFunctions
{
    public static bool GreaterThanOrEqual(byte[] left, byte[] right) => throw new NotImplementedException();
}
var queryable = dbContext.SampleEntities.Where(x => MyDbFunctions.GreaterThanOrEqual(x.Version, version));

Porém, isso nos leva a outro problema. O EF não consegue traduzir a expressão em uma query nativa para o SQL Server.

System.InvalidOperationException: The LINQ expression 'DbSet<SampleEntity>()
    .Where(s => MyDbFunctions.GreaterThanOrEqual(
        left: s.Version, 
        right: __version_0))' could not be translated. Additional information: Translation of method 'MyDbFunctions.GreaterThan' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

Para resolver esse problema, vamos usar um recurso disponível no EF de tradução.

Vamos obter o MethodInfo e registrar o nome do método e a assinatura no DbContext, através do OnModelCreating.

var methodInfo = typeof(MyDbFunctions).GetMethod(nameof(MyDbFunctions.GreaterThanOrEqual), BindingFlags.Static | BindingFlags.Public, [typeof(byte[]), typeof(byte[])]);

Obs: a sintaxe de array é C# 12.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var methodInfo = typeof(MyDbFunctions).GetMethod(nameof(MyDbFunctions.GreaterThanOrEqual), BindingFlags.Static | BindingFlags.Public, [typeof(byte[]), typeof(byte[])]);

    modelBuilder
        .HasDbFunction(methodInfo)
        .HasTranslation(_ => throw new NotImplementedException());
}

O próximo passo é tratar a expression que chega no HasTranslation.

image

A implementação final fica:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var methodInfo = typeof(MyDbFunctions).GetMethod(nameof(MyDbFunctions.GreaterThanOrEqual), BindingFlags.Static | BindingFlags.Public, [typeof(byte[]), typeof(byte[])]);

    modelBuilder
        .HasDbFunction(methodInfo)
        .HasTranslation(args =>
        {
            var leftArg = args[0];
            var expr = new SqlBinaryExpression(ExpressionType.GreaterThanOrEqual, leftArg, args[1], leftArg.Type, leftArg.TypeMapping);

            return expr;
        });
}

image

Resultado do Queryable:

SELECT [s].[Id], [s].[Number], [s].[Version]
FROM [SampleEntities] AS [s]
WHERE [s].[Version] >= @__version_0

O código completo encontra-se no repositório: https://github.com/ircnelson/efcore-custom-functions

Referências:

https://jaliyaudagedara.blogspot.com/2022/03/custom-ef-core-function-to-use-transact.html

https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping#mapping-a-method-to-a-custom-sql

Подобається цей допис?

Купити для Nelson Júnior каву