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

votos
18

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

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

Edit 1

Acabei encontrando a minha própria resposta.

Publicado 21/09/2008 em 06:28
fonte usuário
Em outras línguas...                            


6 respostas

votos
1
tags.Select(testTag => testTag.Name)

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

Respondeu 21/09/2008 em 06:49
fonte usuário

votos
2

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 lá não precisa ser qualquer.

Respondeu 21/09/2008 em 07:01
fonte usuário

votos
3

Somando-lo ...

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

Então é isso que você está esperando?

Estou um pouco confuso.

Respondeu 21/09/2008 em 18:52
fonte usuário

votos
0

Nota: editar 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 que ele vai tentar puxar para baixo tudo, de contentQuerybuscar todas as .Tagscoleções. Eu teria que realmente configurar um servidor para verificar se, no entanto.

Respondeu 22/09/2008 em 05:42
fonte usuário

votos
2

O erro está relacionado à variável os 'tags'. O LINQ to Entities não suporta um parâmetro que é um conjunto de valores. Simplesmente chamando tags.AsQueryable () - como sugerido em uma resposta ealier - também 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 solução alternativa, você pode construir manualmente o filtro utilizando a API de expressão (veja este post do fórum ) 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));
Respondeu 24/09/2008 em 13:27
fonte usuário

votos
18

Depois de ler sobre o PredicateBuilder , ler todos os mensagens maravilhosas que as pessoas enviadas para mim, postar em outros sites, e depois de ler mais sobre Combinando Predicados e Mapeamento função canônica .. oh e eu peguei um pouco de Chamando funções em consultas LINQ ( algumas dessas classes foram tiradas a partir destas páginas).

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

Vamos dar o pedaço cortado 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 para ele, para fazê-lo funcionar. Tive que criar duas exceções (porque era exceções internas Newing Eu também tive que mudar de retorno do método ReadOnlyCollection () 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. Se movendo...

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 correspondência "tag" entidades das marcas 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.

Respondeu 25/09/2008 em 06:04
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more