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

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.

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.

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;
});
}
Resultado do Queryable:
SELECT [s].[Id], [s].[Number], [s].[Version]
FROM [SampleEntities] AS [s]
WHERE [s].[Version] >= @__version_0O 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
