Não é possível refatorar usando LINQ to Entities e LinqKit/PredicateBuilder
Pergunta
Eu tenho tentado refatorar uma expressão LINQ em um método e encontrei os dois "Internal .NET Framework Data Provider error 1025.
" e "The parameter 'xyz' was not bound in the specified LINQ to Entities query expression.
"exceções.
Aqui estão as partes relevantes do modelo de entidade (usando EF 4.2/LINQ to Entities):
public class Place : Entity
{
public string OfficialName { get; protected internal set; }
public virtual ICollection<PlaceName> { get; protected internal set; }
}
public class PlaceName : Entity
{
public string Text { get; protected internal set; }
public string AsciiEquivalent { get; protected internal set; }
public virtual Language TranslationTo { get; protected internal set; }
}
public class Language : Entity
{
public string TwoLetterIsoCode { get; protected internal set; }
}
O modelo relacional básico é este:
Place (1) <-----> (0..*) PlaceName (0..*) <-----> (0..1) Language
Estou tentando criar uma consulta que, quando for feita uma pesquisa term
, tente encontrar Place
entidades cujas OfficialName
começa com o term
OU quem tem PlaceName
cujo Text
ou AsciiEquivalent
começa com a pesquisa term
. (Language
não é onde estou tendo problemas, embora faça parte da consulta, porque PlaceName
s deve corresponder apenas ao CultureInfo.CurrentUICulture.TwoLetterIsoLanguageName
.)
O seguinte código funciona:
internal static IQueryable<Place> WithName(this IQueryable<Place> queryable,
string term)
{
var matchesName = OfficialNameMatches(term)
.Or(NonOfficialNameMatches(term));
return queryable.AsExpandable().Where(matchesName);
}
private static Expression<Func<Place, bool>> OfficialNameMatches(string term)
{
return place => place.OfficialName.StartsWith(term);
}
private static Expression<Func<Place, bool>> NonOfficialNameMatches(string term)
{
var currentLanguage = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
return place => place.Names.Any(
name =>
name.TranslationToLanguage != null
&&
name.TranslationToLanguage.TwoLetterIsoCode == currentLanguage
&&
(
name.Text.StartsWith(term)
||
(
name.AsciiEquivalent != null
&&
name.AsciiEquivalent.StartsWith(term)
)
)
);
}
O que estou tentando fazer a seguir é refatorar o NonOfficialNameMatches
método para extrair o name => ...
expressão em um método separado, para que possa ser reutilizada por outras consultas.Aqui está um exemplo que tentei, que não funciona e lança a exceção "The parameter 'place' was not bound in the specified LINQ to Entities query expression.
":
private static Expression<Func<Place, bool>> NonOfficialNameMatches(string term)
{
return place => place.Names.AsQueryable().AsExpandable()
.Any(PlaceNameMatches(term));
}
public static Expression<Func<PlaceName, bool>> PlaceNameMatches(string term)
{
var currentLanguage = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
return name =>
name.TranslationToLanguage != null
&&
name.TranslationToLanguage.TwoLetterIsoCode == currentLanguage
&&
(
name.Text.StartsWith(term)
||
(
name.AsciiEquivalent != null
&&
name.AsciiEquivalent.StartsWith(term)
)
);
}
Quando eu não tenho o .AsExpandable()
cadeia em NonOfficialNameMatches
, então eu recebo o "Internal .NET Framework Data Provider error 1025.
"exceção.
eu tenho seguido outros conselhos aqui como várias combinações de invocação .Expand()
nos predicados, mas sempre termina com uma das exceções acima mencionadas.
É possível fatorar essa expressão em um método separado usando LINQ to Entities com LinqKit/PredicateBuilder? Se sim, como?O que estou fazendo de errado?
Solução
O método abaixo deve funcionar:
private static Expression<Func<Place, bool>> NonOfficialNameMatches(string term)
{
Expression<Func<PlaceName, bool>> placeNameExpr = PlaceNameMatches(term);
Expression<Func<Place, bool>> placeExpr =
place => place.Names.Any(name => placeNameExpr.Invoke(name));
return placeExpr.Expand();
}
EDITAR:Adicionando explicações adicionais
O PlaceNameMatches
método funciona como você escreveu.Seus problemas estavam em como você usou o método.Se você quiser fatorar partes de uma expressão siga os 3 passos que fiz no método acima.
Defina uma variável local para a expressão criada por um método.
Defina outra variável local para uma nova expressão que invoque a expressão da variável local.
Ligue para o LinkKit
Expand
método:isso expandirá quaisquer expressões invocadas