LINQ to Entities - Edifício onde cláusulas para coleções de teste dentro de um relacionamento muitos para muitos

StackOverflow https://stackoverflow.com/questions/110314

Pergunta

Então, eu estou usando a estrutura de entidade Linq. Eu tenho 2 entidades: Content e Tag. Eles estão em um relacionamento muitos-para-muitos com o outro. Content pode ter muitos Tags e Tag pode ter muitos Contents. Então, eu estou tentando escrever uma consulta para selecionar todos os conteúdos, onde os nomes tags são igual a blah

As entidades ambos têm uma coleção de outra entidade como uma propriedade (mas não IDs). Aqui é onde eu estou lutando. Eu tenho uma expressão personalizada para Contains (assim, quem pode me ajudar, você pode supor que eu posso fazer um "contém" para uma coleção). Eu tenho essa expressão de: http://forums.microsoft.com/ MSDN / ShowPost.aspx? PostID = 2670710 & SiteID = 1

Editar 1

acabei encontrando minha própria resposta.

Foi útil?

Solução

Depois de ler sobre a PredicateBuilder , ler todos os mensagens maravilhosas que as pessoas enviadas para me, postando em outros sites, e depois de ler mais em Combinando Predicados e Canonical função de mapeamento .. oh e eu peguei um pouco de funções em consultas LINQ Calling (algumas delas aulas foram tiradas a partir destas páginas).

Finalmente tenho uma solução !!! Embora não haja uma peça que é um pouco hackeado ...

Vamos começar a peça hackeada de novo com: (

Eu tive que usar reflector e copiar a classe ExpressionVisitor que está marcado como interno. Então eu tive que fazer algumas pequenas alterações que, para obtê-lo a trabalhar. Tive que criar duas exceções (porque foi Newing exceções internas Eu também tive que mudar de retorno do método ReadOnlyCollection () a partir de:.

return sequence.ToReadOnlyCollection<Expression>();

Para:

return sequence.AsReadOnly();

Eu ia postar a classe, mas é bastante grande e eu não quero encher este post mais do que já vai ser. Espero que no futuro essa classe pode ser removido da minha biblioteca e que a Microsoft vai torná-la pública. Continuando ...

Eu adicionei uma classe ParameterRebinder:

public class ParameterRebinder : ExpressionVisitor {
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;

        public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }

        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp) {
            return new ParameterRebinder(map).Visit(exp);
        }

        internal override Expression VisitParameter(ParameterExpression p) {
            ParameterExpression replacement;
            if (map.TryGetValue(p, out replacement)) {
                p = replacement;
            }
            return base.VisitParameter(p);
        }
    }

Então eu adicionei uma classe ExpressionExtensions:

public static class ExpressionExtensions {
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) {
            // build parameter map (from parameters of second to parameters of first)
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with parameters from the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // apply composition of lambda expression bodies to parameters from the first expression 
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
            return first.Compose(second, Expression.And);
        }

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
            return first.Compose(second, Expression.Or);
        }
    }

E a última classe eu adicionei foi PredicateBuilder:

public static class PredicateBuilder {
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

}

Este é o meu resultado ... eu era capaz de executar esse código e voltar as resultantes entidades "conteúdo" que têm correspondentes entidades "tag" das etiquetas que eu estava procurando!

    public static IList<Content> GetAllContentByTags(IList<Tag> tags) {
        IQueryable<Content> contentQuery = ...

        Expression<Func<Content, bool>> predicate = PredicateBuilder.False<Content>();

        foreach (Tag individualTag in tags) {
            Tag tagParameter = individualTag;
            predicate = predicate.Or(p => p.Tags.Any(tag => tag.Name.Equals(tagParameter.Name)));
        }

        IQueryable<Content> resultExpressions = contentQuery.Where(predicate);

        return resultExpressions.ToList();
    }

Por favor, deixe-me saber se alguém precisa de ajuda com a mesma coisa, se você gostaria de me para lhe enviar arquivos para isso, ou só precisa de mais informações.

Outras dicas

Somando-lo ...

contentQuery.Where(
    content => content.Tags.Any(tag => tags.Any(t => t.Name == tag.Name))
);

Assim é que o que você está esperando?

Estou um pouco confuso.

Isto é o que a própria pergunta pede:

contentQuery.Where(
    content => content.Tags.Any(tag => tag.Name == "blah")
);

Eu não tenho certeza do que o processo de pensamento era chegar ao código do questionador, realmente, e eu não sou inteiramente certo exatamente o que a sua realmente fazendo. A única coisa que eu tenho certeza é de que .AsQueryable () é completamente desnecessário - ou .Tags já é um IQueryable ou o .AsQueryable () é apenas vai fingir para você - adicionando chamadas extras em onde que não precisa ser o caso.

O erro está relacionado à variável os 'tags'. LINQ to Entities não suporta um parâmetro que é um conjunto de valores. Simplesmente chamando tags.AsQueryable () - como sugerido em uma resposta ealier -. Não vai funcionar porque o padrão em memória provedor de consulta LINQ não é compatível com LINQ to Entities (ou outros prestadores relacionais)

Como alternativa, você pode construir manualmente o filtro utilizando a API de expressão (veja este fórum pós ) e aplicá-la da seguinte forma:

var filter = BuildContainsExpression<Element, string>(e => e.Name, tags.Select(t => t.Name));
var query = source.Where(e => e.NestedValues.Any(filter));
tags.Select(testTag => testTag.Name)

Onde é que a variável de etiquetas é inicializado a partir de? O que é isso?

NOTA: editar por favor a questão em si, em vez de responder com uma resposta - isto não é uma linha de discussão, e eles podem voltar a pedir-se a qualquer momento

Se você estiver procurando por todos os conteúdos que são marcados com qualquer um de um conjunto de tags:

IEnumerable<Tag> otherTags;
...
var query = from content in contentQuery
            where content.Tags.Intersection(otherTags).Any()
            select content;

Parece que você pode estar usando LINQ to SQL, caso em que pode ser melhor se você escrever um procedimento armazenado para fazer isso um: usando LINQ para fazer isso provavelmente não será executado no SQL Server - é muito provável vai tentar puxar para baixo tudo, desde contentQuery e buscar todas as coleções .Tags. Eu teria que realmente configurar um servidor para verificar que, apesar de tudo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top