NeinLinq fornece extensões úteis para usar provedores LINQ, como Entity Framework, que suportam apenas um subconjunto menor de funções .NET, reutilizando funções, reescrevendo consultas, até mesmo tornando-as seguras para nulos, e construindo consultas dinâmicas usando predicados e seletores traduzíveis.
Para oferecer suporte a diferentes implementações de LINQ, os seguintes tipos estão disponíveis. Escolha pelo menos um.
Use NeinLinq para consultas LINQ simples:
PM > Install-Package NeinLinq
Use NeinLinq.Async para consultas LINQ assíncronas:
PM > Install-Package NeinLinq.Async
Use NeinLinq.EntityFramework para consultas LINQ do Entity Framework 6:
PM > Install-Package NeinLinq.EntityFramework
Use NeinLinq.EntityFrameworkCore para consultas LINQ do Entity Framework Core:
PM > Install-Package NeinLinq.EntityFrameworkCore
Nota: os métodos de extensão descritos abaixo possuem nomes diferentes dependendo do pacote escolhido acima, para evitar alguns conflitos! Por exemplo, existem:
ToInjectable
( NeinLinq )ToAsyncInjectable
( NeinLinq.Async )ToDbInjectable
( NeinLinq.EntityFramework )ToEntityInjectable
( NeinLinq.EntityFrameworkCore )O uso de sabores específicos é incentivado para EF6/EFCore (caso contrário, as consultas assíncronas não funcionarão).
Novo: com a versão 5.1.0
o pacote NeinLinq.EntityFrameworkCore introduziu uma extensão DbContext
explícita para habilitar a injeção Lambda globalmente:
services . AddDbContext < MyContext > ( options =>
options . UseSqlOrTheLike ( " ... " ) . WithLambdaInjection ( ) ) ;
Nota: a chamada para WithLambdaInjection
precisa acontecer após a chamada para UseSqlOrTheLike
!
Muitos provedores LINQ só podem suportar um subconjunto muito pequeno de funcionalidades do .NET, e nem mesmo podem suportar nossas próprias "funções". Digamos que implementamos um método simples LimitText
e o usamos em uma consulta LINQ comum, que será traduzida para SQL por meio do Entity Framework ...
LINQ to Entities não reconhece o método 'System.String LimitText(System.String, Int32)' e esse método não pode ser traduzido em uma expressão de armazenamento.
Isto é o que obtemos; na verdade, é realmente irritante. Temos que espalhar nossa lógica entre o código, que será traduzido por qualquer provedor de consulta LINQ, e o código, que não será. Pior ainda: se alguma lógica for “traduzível”, o que é bom, temos que copiar e colar! Consolidar o código dentro de uma função comum não funciona, pois o provedor não consegue traduzir essa chamada de método simples. Ah, sim.
Vamos apresentar a "injeção lambda":
[ InjectLambda ]
public static string LimitText ( this string value , int maxLength )
{
if ( value != null && value . Length > maxLength )
return value . Substring ( 0 , maxLength ) ;
return value ;
}
public static Expression < Func < string , int , string > > LimitText ( )
{
return ( v , l ) => v != null && v . Length > l ? v . Substring ( 0 , l ) : v ;
}
// -------------------------------------------------------------------
from d in data . ToInjectable ( )
select new
{
Id = d . Id ,
Value = d . Name . LimitText ( 10 )
}
Se uma consulta for marcada como "injetável" ( ToInjectable()
) e uma função usada nesta consulta for marcada como "injetar aqui" ( [InjectLambda]
), o mecanismo de reescrita do NeinLinq substitui a chamada do método pela expressão lambda correspondente, que pode ser traduzido para SQL ou qualquer outra coisa. Assim, somos capazes de encapsular funcionalidades .NET não suportadas e até mesmo criar as nossas próprias. Yay.
[ InjectLambda ]
public static bool Like ( this string value , string likePattern )
{
throw new NotImplementedException ( ) ;
}
public static Expression < Func < string , string , bool > > Like ( )
{
return ( v , p ) => SqlFunctions . PatIndex ( p , v ) > 0 ;
}
// -------------------------------------------------------------------
from d in data . ToInjectable ( )
where d . Name . Like ( " %na_f% " )
select .. .
Este é um exemplo de como podemos abstrair a classe SqlFunctions
do Entity Framework para usar um método de extensão Like
(espero) mais agradável em nosso código de consulta - PatIndex
provavelmente é usado para simular uma instrução SQL LIKE, por que não torná-lo assim? Na verdade, podemos implementar o método "comum" com a ajuda de expressões regulares para executar nosso código sem tocar em SqlFunctions
também...
Novo: com a versão 7.0.0
o NeinLinq agora oferece suporte a provedores personalizados para o InjectLambdaAttribute. Este recurso permite que você defina sua própria lógica para determinar quais métodos devem ser considerados injetáveis sem adicionar explicitamente o atributo [InjectLambda]
. Isto é particularmente útil ao trabalhar com bibliotecas externas ou códigos que você não pode modificar diretamente:
var oldProvider = InjectLambdaAttribute . Provider ;
InjectLambdaAttribute . SetAttributeProvider ( memberInfo =>
{
// Your custom logic here
if ( memberInfo . Name . StartsWith ( " ExternalMethod " ) )
{
return new InjectLambdaAttribute ( typoef ( MyType ) , nameof ( MyType . ExternalMethodExpression ) ) ;
}
// fallback to standard provider
return oldProvider ( memberInfo ) ;
} ) ;
Finalmente, vejamos esta consulta usando Entity Framework ou algo semelhante:
from d in data . ToInjectable ( )
let e = d . RetrieveWhatever ( )
where d . FulfillsSomeCriteria ( )
select new
{
Id = d . Id ,
Value = d . DoTheFancy ( e )
}
// -------------------------------------------------------------------
[ InjectLambda ]
public static Whatever RetrieveWhatever ( this Entity value )
{
throw new NotImplementedException ( ) ;
}
public static Expression < Func < Entity , Whatever > > RetrieveWhatever ( )
{
return d => d . Whatevers . FirstOrDefault ( e => .. . ) ;
}
[ InjectLambda ]
public static bool FulfillsSomeCriteria ( this Entity value )
{
throw new NotImplementedException ( ) ;
}
public static Expression < Func < Entity , bool > > FulfillsSomeCriteria ( )
{
return d => .. .
}
[ InjectLambda ]
public static decimal DoTheFancy ( this Entity value , Whatever other )
{
throw new NotImplementedException ( ) ;
}
public static Expression < Func < Entity , Whatever , decimal > > DoTheFancy ( )
{
return ( d , e ) => .. .
}
Os métodos RetrieveWhatever
, FulfillsSomeCriteria
e DoTheFancy
devem ser marcados adequadamente, usando o atributo [InjectLambda]
ou apenas a simples convenção "mesma classe, mesmo nome, assinatura correspondente" (que, a propósito, exige que a classe esteja listada em verde). E a chamada ToInjectable
pode acontecer em qualquer lugar da cadeia de consulta LINQ, portanto não precisamos poluir nossa lógica de negócios.
Nota: a duplicação de código não deve ser necessária. O método comum pode apenas compilar a expressão, de preferência apenas uma vez. Uma solução direta pode se parecer com o exemplo de código a seguir (é possível encapsular/organizar esse material, por mais sofisticado que pareça adequado, o NeinLinq não tem requisitos específicos; sinta-se à vontade para usar o "Expression Cache" integrado ou construir algo sofisticado... ):
public static CachedExpression < Func < string , int , string > > LimitTextExpr { get ; }
= new ( ( v , l ) => v != null && v . Length > l ? v . Substring ( 0 , l ) : v )
[ InjectLambda ]
public static string LimitText ( this string value , int maxLength )
=> LimitTextExpr . Compiled ( value , maxLength ) ;
Avançado: também funciona com métodos de instância, portanto, o código da expressão real é capaz de recuperar dados adicionais. Até mesmo interfaces e/ou classes base podem ser usadas para abstrair todas as coisas. Assim, podemos declarar uma interface/classe base sem expressões, mas fornecer a expressão para injetar usando herança.
public class ParameterizedFunctions
{
private readonly int narf ;
public ParameterizedFunctions ( int narf )
{
this . narf = narf ;
}
[ InjectLambda ( " FooExpr " ) ]
public string Foo ( )
{
.. .
}
public Expression < Func < string > > FooExpr ( )
{
.. . // use the narf!
}
}
// -------------------------------------------------------------------
public interface IFunctions
{
[ InjectLambda ]
string Foo ( Entity value ) ; // use abstraction for queries
}
public class Functions : IFunctions
{
[ InjectLambda ]
public string Foo ( Entity value )
{
.. .
}
public Expression < Func < Entity , string > > Foo ( )
{
.. .
}
}
Nota : injetar métodos de instância não é tão eficiente quanto injetar métodos estáticos. Só não use os anteriores, se não for realmente necessário. Além disso, injetar métodos de instância do tipo selado reduz um pouco a sobrecarga, pois há mais coisas que precisam ser feitas apenas uma vez. Ok, nada de novo a dizer aqui.
Mais uma coisa: para ser mais hacky e usar menos cerimônia de métodos de extensão, é possível injetar propriedades diretamente nos modelos.
public class Model
{
public double Time { get ; set ; }
public double Distance { get ; set ; }
[ InjectLambda ]
public double Velocity => Distance / Time ;
public static Expression < Func < Model , double > > VelocityExpr => v => v . Distance / v . Time ;
}
Novamente, em vez de colocar [InjectLambda]
em tudo, é possível adicionar todos os modelos à lista verde enquanto chama ToInjectable
.
Estamos escrevendo o ano de 2024 e ainda precisamos nos preocupar com valores nulos.
De qualquer forma, nos acostumamos e estamos bem. Mas escrever consultas em C# carregadas com verificações nulas não parece certo, apenas parece horrível, o SQL traduzido fica ainda pior. Uma consulta LINQ apenas para bancos de dados SQL pode poupar essas verificações nulas; uma consulta LINQ apenas para cálculos na memória deve incluí-las. E uma consulta LINQ para ambos tem um problema (teste unitário?), que o NeinLinq tenta resolver.
A consulta a seguir pode acionar referências nulas:
from a in data
orderby a . SomeInteger
select new
{
Year = a . SomeDate . Year ,
Integer = a . SomeOther . SomeInteger ,
Others = from b in a . SomeOthers
select b . SomeDate . Month ,
More = from c in a . MoreOthers
select c . SomeOther . SomeDate . Day
}
Embora a consulta a seguir não deva :
from a in data
where a != null
orderby a . SomeInteger
select new
{
Year = a . SomeDate . Year ,
Integer = a . SomeOther != null
? a . SomeOther . SomeInteger
: 0 ,
Others = a . SomeOthers != null
? from b in a . SomeOthers
select b . SomeDate . Month
: null ,
More = a . MoreOthers != null
? from c in a . MoreOthers
select c . SomeOther != null
? c . SomeOther . SomeDate . Day
: 0
: null
}
Talvez tenhamos esquecido algum cheque? Ou podemos relaxar graças ao NeinLinq :
from a in data . ToNullsafe ( )
orderby a . SomeInteger
select new
{
Year = a . SomeDate . Year ,
Integer = a . SomeOther . SomeInteger ,
Others = from b in a . SomeOthers
select b . SomeDate . Month ,
More = from c in a . MoreOthers
select c . SomeOther . SomeDate . Day
}
Como acontece com todo auxiliar ToWhatever
em NeinLinq , ToNullsafe
pode ser chamado em qualquer lugar da cadeia de consulta LINQ.
Muitos aplicativos orientados a dados precisam construir algum tipo de consulta dinâmica. Isso pode levar a manipulações sujas de strings, encanamento complexo de árvores de expressão ou uma combinação desses. Conjunções simples e / ou -conjunções já são resolvidas dentro de outras bibliotecas, mas conjunções de predicados "estrangeiros" não são tão fáceis assim.
Pensemos em três entidades: Academia tem Cursos, Cursos tem Palestras.
Expression < Func < Course , bool > > p = c => .. .
Expression < Func < Course , bool > > q = c => .. .
db . Courses . Where ( p . And ( q ) ) .. .
Ok, já sabemos disso.
Expression < Func < Academy , bool > > p = a => .. .
db . Courses . Where ( p . Translate ( )
. To < Course > ( c => c . Academy ) ) .. .
Agora podemos traduzir um predicado (combinado) para uma entidade pai...
Expression < Func < Lecture , bool > > p = l => .. .
db . Courses . Where ( p . Translate ( )
. To < Course > ( ( c , q ) => c . Lectures . Any ( q ) ) ) .. .
..e até mesmo para entidades filhas.
Vamos usar tudo isso como uma conclusão:
IEnumerable < Expression < Func < Academy , bool > > > predicatesForAcademy = .. .
IEnumerable < Expression < Func < Course , bool > > > predicatesForCourse = .. .
IEnumerable < Expression < Func < Lecture , bool > > > predicatesForLecture = .. .
var singlePredicateForAcademy =
predicatesForAcademy . Aggregate ( ( p , q ) => p . And ( q ) ) ;
var singlePredicateForCourse =
predicatesForCourse . Aggregate ( ( p , q ) => p . And ( q ) ) ;
var singlePredicateForLecture =
predicatesForLecture . Aggregate ( ( p , q ) => p . And ( q ) ) ;
var academyPredicateForCourse =
singlePredicateForAcademy . Translate ( )
. To < Course > ( c => c . Academy ) ;
var coursePredicateForCourse =
singlePredicateForCourse ; // the hard one ^^
var lecturePredicateForCourse =
singlePredicateForLecture . Translate ( )
. To < Course > ( ( c , p ) => c . Lectures . Any ( p ) ) ;
var finalPredicate =
academyPredicateForCourse . And ( coursePredicateForCourse )
. And ( lecturePredicateForCourse ) ;
db . Courses . Where ( finalPredicate ) .. .
Além disso, nenhum Invoke é usado para conseguir isso: muitos provedores LINQ não o suportam ( Entity Framework , estou olhando para você...), então esta solução deve ser bastante compatível.
Tal como acontece com os seletores de predicados, os seletores também precisam de um pouco de atenção. Se já tivermos um seletor para algum tipo base e quisermos reutilizar esse código para um ou mais tipos concretos, seremos forçados a copiar e colar novamente. Não faça isso!
Vamos pensar em duas entidades (Academy e SpecialAcademy) com Contratos/ViewModels/DTOs/Whatever (AcademyView e SpecialAcademyView) correspondentes.
Expression < Func < Academy , AcademyView > > s =
a => new AcademyView { Id = a . Id , Name = a . Name } ;
Expression < Func < SpecialAcademy , SpecialAcademyView > > t =
a => new SpecialAcademyView { Narf = a . Narf } ;
Observe que omitimos as ligações Member do primeiro seletor no segundo. Não se repita, lembra?
db . Academies . OfType < SpecialAcademy > ( )
. Select ( s . Translate ( )
. Cross < SpecialAcademy > ( )
. Apply ( t ) ) ;
Embora existam mais opções, o cenário comum pode ser o seguinte: reutilizar o seletor base, iniciar sua tradução (inferência de tipo), dizer por onde começar (sem inferência de tipo) e, finalmente, aplicar o seletor adicional (inferência de tipo, novamente) .
Agora consideremos as relações pais/filhos (Academia e Curso).
Expression < Func < Academy , AcademyView > > s =
a => new AcademyView { Id = a . Id , Name = a . Name } ;
Expression < Func < Course , CourseView > > t =
c => new CourseView { Id = c . Id , Name = c . Name } ;
db . Courses . Select ( s . Translate ( )
. Cross < Course > ( c => c . Academy )
. Apply ( c => c . Academy , t ) ) ;
db . Academies . Select ( t . Translate ( )
. Cross < Academy > ( ( a , v ) => a . Courses . Select ( v ) )
. Apply ( a => a . Courses , s ) ) ;
Novamente, além de outras opções, podemos traduzir de pai para filho: reutilizar o seletor pai, iniciar sua tradução, dizer por onde começar (dado o caminho para sua entidade pai) e, finalmente, aplicar o seletor adicional (dado o caminho para sua entidade pai). "visualização" principal). E podemos traduzir de outra forma também: reutilizar o seletor filho, iniciar sua tradução, dizer por onde começar (dada uma expressão para selecionar os filhos) e, finalmente, aplicar o seletor adicional...
Para ser mais flexível, a "Tradução da fonte" / "Tradução do resultado" pode ser usada individualmente:
Expression < Func < Academy , AcademyView > > selectAcademy =
a => new AcademyView { Id = a . Id , Name = a . Name } ;
var selectCourseWithAcademy =
selectAcademy . Translate ( )
. Source < Course > ( c => c . Academy )
. Translate ( )
. Result < CourseView > ( c => c . Academy )
. Apply ( a => new CourseView
{
Id = a . Id ,
Name = a . Name
} ) ;
Nota: para ser menos detalhado, "Tradução da fonte"/"Tradução do resultado" pode ser usada em uma única declaração inchada, se apropriado: