Fórum Uso do LINQ para vários códigos #7233

01/07/2009

0

Boa tarde!

Eu tenho uma situação da qual preciso utilizar o LINQ para trazer registro conforme vários códigos selecionados pelo usuário. Para isso, foi feito o código abaixo:

public List<PessoaDeficiencia> SelecionarDeficienteFisico(List<PessoaStatus> pStatusPessoa)
        {
            List<PessoaDeficiencia> listPessoaDeficiencia = new List<PessoaDeficiencia>();

            try
            {
                var consulta = from pessoadeficiencia in _context.tab_pessoa_deficiencia
                               where (pessoadeficiencia.tab_pessoa.possui_deficiencia == "S")
                               && (pessoadeficiencia.tab_pessoa.tab_tipo_contratacao.c_def_fisico == "S")
                               && (pessoadeficiencia.tab_pessoa.tab_pessoa_status.id_status_pessoa != 2)
                               && ((0 == pStatusPessoa[0].IdStatusPessoa)
                               || (from pessoastatus in pStatusPessoa select pessoastatus.IdStatusPessoa).Contains(pessoadeficiencia.tab_pessoa.tab_pessoa_status.id_status_pessoa))
                               select new
                               {
                                   pessoadeficiencia.id_pessoa_deficiencia,
                                   pessoadeficiencia.tab_pessoa.Id_pessoa,
                                   pessoadeficiencia.tab_tipo_deficiente_fisico.id_tipo_deficiencia,
                                   pessoadeficiencia.path
                               };
                           .
                           .
                           .
                           .

Foi utilizado um parâmetro, que é uma lista de uma classe. Nesta Lista, uma das propriedades é referente ao código. Por isso, com uma lista de vários códigos, gostaria de fazer uso do LINQ como se fosse na sintaxe do SELECT usando a instrução IN (SELECT * FROM TABELA WHERE Codigo IN (1,2,3,4,...)...

Mas, ao tentar realizar a consulta, gerou uma exceção de conflito entre uma lista de array do tipo int com o próprio tipo int (na linha "(from pessoastatus in pStatusPessoa select pessoastatus.IdStatusPessoa).Contains(pessoadeficiencia.tab_pessoa.tab_pessoa_status.id_status_pessoa))".

Por isso gostaria de saber se alguém poderia me ajudar a como realizar esta tarefa fazendo seleção pelo LINQ na condição de usar vários código como se faz no SELECT com a palavra-chave IN??

Neste exemplo, está sendo usado LINQ to Entities

Obrigado, fico no aguardo!!
Carlos Nogueira

Carlos Nogueira

Responder

Posts

01/07/2009

Carlos Nogueira

Só para acrescentar com o que foi informado na mensagem anterior, já foi feito outros testes para informar estes códigos com o método Contains(), fazendo uso de ArrayList ou de List<int> para armazenar estes códigos e usar da sintaxe do LINQ, mas mesmo assim, apresentou uma exceção. A descrição da exceção segue abaixo:

LINQ to Entities não reconhece o método 'Boolean Contains(Int32)', que não pode ser convertido em uma expressão de armazenamento.
Responder

Gostei + 0

01/07/2009

Luiz Maia

Ola Carlos,   Para fazer o que necessita, vc precisa usar apenas um Array:   int[] productList = new int[] { 1, 2, 3, 4 }; var myProducts = from p in db.Products where productList.Contains(p.ProductID) select p;Espero ter ajudado.
Por favor me de um retorno se conseguiu, ok?
Ja fiz desta forma e funciona perfeitamente.
Agora, outra forma de fazer é criar um coleção e depois comparar. Veja o exemplo abaixo: AdventureWorks.DB db=new DB(); var itemQuery = from cartItems in db.SalesOrderDetails where cartItems.SalesOrderID == 75144 select cartItems.ProductID; E depois vc compara: var myProducts = from p in db.Products where itemQuery.Contains(p.ProductID) select p; AbraçosAttLuiz Maia
Responder

Gostei + 0

02/07/2009

Carlos Nogueira

Entendo. Gostaria de implementar uma solução em que não fosse necessário fazer utilização de duas querys para resolver o problema. Em relação ao primeiro exemplo, já fiz este tipo de simulação, porém os códigos contidos no array podem variar de uma hora para outra, conforme seleção do usuário (por este motivo coloquei um parâmetro no método para receber um ou mais códigos selecionados pelo usuário). Por isso que no post anterior informei as simulações feitas dom ArrayList e List<int> para fazer com que esta lista de códigos fosse dinâmica para informar na sintaxe do linq, porém ao fazer isso gera a exceção informada pelo método Contains() que não possui 'expressão booleana". Existe a possibilidade de implementar uma solução nestes moldes, isto é, que seja solucionado numa única query com esta list de códigos dinâmicas? Por exemplo, existe alguma foma de fazer com que o array int[] citado por você no exemplo seja dinâmico, isto é, a cada chamada do método montar este array de int com a sintaxe int[]? Tentei fazer isso com as classes ArrayList e List<int> mas não conseguiu! Eu li em alguns sites (blogs) que a Microsoft pretende disponibilizar mais expressões para o método Contains() numa próxima versão do LINQ, isso é mesmo verdade? Bem, fico no aguardo, e obrigado pela atenção!
Responder

Gostei + 0

02/07/2009

Luiz Maia

Ola Carlos,   Nada impede que seus dados sejam dinâmicos. Criei um array de inteiros estatico abaixo somente para testes. Faça um metodo que retorna estes arrays de inteiro para vc então. Pegue seus dados dinamicos, por exemplo "1,3,5,6,7,8,9,12,13..". Vc tem estes dados que recuperou em outro metodo, correto? Agora monte um metodo que transforma isto num array, usando um "split" e depois um "for". Sabe como fazer?   Aguardo seu retorno.   Abraços Att Luiz Maia
Responder

Gostei + 0

02/07/2009

Carlos Nogueira

Para ser sincero não. Você poderia me passar um exemplo de como eu poderia proceder para implementar esta forma dinâmica como você mencionou na mensagem anterior?
Responder

Gostei + 0

02/07/2009

Luiz Maia

Carlos,   Uma solução bem mais estrutura e com bases em boas praticas de codificação seria usar um Stored Procedure para isto, veja um exemplo abaixo:   public static List<Entity.Estado> Pesquisar(string siglaEstado, string nomeEstado, string pais) { Entity.DataClassesDataContext dataClass = new Entity.DataClassesDataContext(); var est = dataClass.SP_PESQUISAR_ESTADO(siglaEstado, nomeEstado, pais); List<Entity.Estado> estado = est.ToList(); return estado; }   Assim  toda logica de subquerys e subselects estariam dentro da Procedure, fica muito melhor e mais legivel. Mas mesmo assim, ja estou desenvolvendo a outra alternativa aqui e te mando agorinha, caso não queira usar SPs.   Abraços Att   Luiz Maia
Responder

Gostei + 0

02/07/2009

Luiz Maia

Ola Carlos,   Conforme te prometi, segue o projeto funcionando. Estou usando o banco de dados NorthWind, que vc pode baixar gratuitamente no site da MircroSoft.   Algumas considerações:   1 - Este é a estrutura do meu arquivo .dbml, que usei no teste:     2 - Criei um DataList que lista as Categorias, alterei a propriedade de Seleção para MultiModo, assim consigo selecionar mais de um item ao mesmo tempo.       3 - Depois de marcar todas as categorias que quero que sejam listados os produtos, faço o binding no GrigView:   using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } private int[] RecuperarCategoriasSelecao() { int[] codigos = new int[ListBox1.Items.Count]; int cont = 0; foreach (ListItem item in ListBox1.Items) { if (item.Selected) { codigos[cont] = Convert.ToInt32(item.Value); cont++; } } return codigos; } public List<Product> RetornaDados() { DataClassesDataContext dataClass = new DataClassesDataContext(); int[] codCategoriasSelecao = RecuperarCategoriasSelecao(); var prod = from p in dataClass.Products where codCategoriasSelecao.Contains((int)p.CategoryID) select p; List<Product> produtos = prod.ToList(); return produtos; }   protected void Button1_Click(object sender, EventArgs e) { GridView1.DataSource = RetornaDados(); GridView1.DataBind(); } } O codigo completo vc pode baixar aqui:   https://www.devmedia.com.br/imagens/discovirtual/200237/Consultoria/LinqComSubSelect.zip     Veja o resultado funcionando abaixo, repare que filtrei pelas categorias Bevereges e Confections, com Ids 1 e 3 respectivamente, pode conferir na grid na coluna CategoryId:         Espero ter ajudado. Abraços   Att Luiz Maia
Responder

Gostei + 0

04/07/2009

Luiz Maia

Ola Carlos,   Como esta indo? Esta conseguindo implementar?   Aguardo um retorno. Abraços Att Luiz Maia
Responder

Gostei + 0

06/07/2009

Carlos Nogueira

Oi!
Eu implementei, o código ficou como você pode visualizar abaixo:

private int[] ObterCodigosSelecao(List<PessoaStatus> pPessoaStatus)
        {
            int[] _codigos = new int[pPessoaStatus.Count];
            int _cont = 0;

            foreach (PessoaStatus item in pPessoaStatus)
            {
                _codigos[_cont] = item.IdStatusPessoa;
                _cont++;
            }

            return _codigos;
        }

public List<PessoaDeficiencia> SelecionarDeficienteFisico(List<PessoaStatus> pPessoaStatus)
        {
            List<PessoaDeficiencia> listPessoaDeficiencia = new List<PessoaDeficiencia>();

            try
            {
                int[] _codStatusSelecao = ObterCodigosSelecao(pPessoaStatus);

                var consulta = from pessoadeficiencia in _context.tab_pessoa_deficiencia
                               where (pessoadeficiencia.tab_pessoa.possui_deficiencia == "S")
                               && (pessoadeficiencia.tab_pessoa.tab_tipo_contratacao.c_def_fisico == "S")
                               && (pessoadeficiencia.tab_pessoa.tab_pessoa_status.id_status_pessoa != 2)
                               && ((0 == pPessoaStatus[0].IdStatusPessoa)
                               || (_codStatusSelecao.Contains(pessoadeficiencia.tab_pessoa.tab_pessoa_status.id_status_pessoa)))
                               select new
                               {
                                   pessoadeficiencia.id_pessoa_deficiencia,
                                   pessoadeficiencia.tab_pessoa.Id_pessoa,
                                   pessoadeficiencia.tab_tipo_deficiente_fisico.id_tipo_deficiencia,
                                   pessoadeficiencia.path
                               };

                var listConsulta = consulta.ToList();

                foreach (var list in listConsulta)
                {
                    PessoaDeficiencia pessoadeficiencia = new PessoaDeficiencia();
                    pessoadeficiencia.IdPessoaDeficiencia = list.id_pessoa_deficiencia;
                    pessoadeficiencia.Pessoa = new Pessoa(list.Id_pessoa);
                    pessoadeficiencia.TipoDeficienteFisico = new TipoDeficienteFisico(list.id_tipo_deficiencia);
                    pessoadeficiencia.Path = list.path;
                   
                    listPessoaDeficiencia.Add(pessoadeficiencia);
                }

                return listPessoaDeficiencia;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

No método SelecionarDeficienteFisico, ao passar pela linha "varlisConsulta = consulta.ToList()" ele gerou uma exceção com a seguinte mensagem:

"LINQ to Entities não reconhece o método 'SMA.GENTEv1.PessoaStatus get_Item(Int32)', que não pode ser convertido em uma expressão de armazenamento."

O que você acha que pode ser, ou o que devo fazer para contornar isso?
Responder

Gostei + 0

06/07/2009

Luiz Maia

Ola Carlos,   Tente algo do tipo:   if (consulta.Count() == 1) { pessoadeficiencia.IdPessoaDeficiencia = consulta.Single().id_pessoa_deficiencia;
// e assim por diante... }   Abraços E aguardo retorno, ok? Att Luiz Maia
Responder

Gostei + 0

06/07/2009

Carlos Nogueira

Oi Luiz!

Efetuei o teste, mas gerou a mesma exceção citada na mensagem anterior. E com o foreach neste método já consigo tratar o resultado da consulta se tiver apenas um registro, vários registros ou nenhum registro, ao invés de efetuar tratamento com o if.
Mas bem, estou enviando a mensagem que gerou a mesma exceção ainda. Quando foi passar na linha que você sugeriu, "if (consulta.Count() == 1)", ocorreu a exceção.
Responder

Gostei + 0

06/07/2009

Luiz Maia

Ola,   tente fazer o seguinte, retirar a linha que esta dando excessao, e usar direto no foreach:   foreach (var list in Consulta.ToList()) Tente algo assim, e caso de alguma excessao me mande o print da tela por favor, antes tente debugar a aplicação e ver se o list foi populado. Aguardo seu retorno, Abraços   Att Luiz Maia
Responder

Gostei + 0

07/07/2009

Carlos Nogueira

Oi Luiz!

Conforme você havia pedido, eu fiz a alteração no foreach, e ao debugar passando pelo consulta.ToList() ele gera a exceção. Abaixo, vou colocar as imagens que obtive no momento de debugar.

Esta imagem se refere a uma chamada de teste para o método, onde alimente um List com algumas informações para passar para o parâmetro:


Agora esta imagem se refere no momento que obtive os códigos para utilizar no LINQ, aquele exemplo que você me passou para obter de forma dinâmica:


Esta imagem se refere a exceção que foi gerada conforma havia comentado acima, quando passa pela instrução consulta.ToList():


E decide colocar também esta imagem do quick watch em relação aa variável consulta após ter passado pela sintaxe do LINQ:


Bem, essas são as imagens que capturei para lhe enviar. Fico no aguardo de um retorno. Obrigado!
Responder

Gostei + 0

07/07/2009

Luiz Maia

Ola Carlos,   Realmente o que esta querendo fazer nao funcionado para o Linq to Entities, mas funciona para Linq to SQL,  acordo com o proprio pessoal da Microsoft:   Diogo Vega faz parte do ADO.Net Team, ele mandou a seguinte explanação sobre a questão do L2E:   "By design, LINQ to Entities requires the whole LINQ query expression to be translated to a server query. Only a few uncorrelated subexpressions (expressions in the query that do not depend on the results from the server) are evaluated on the client before the query is translated. Arbitrary method invocations that do not have a known translation, like GetProducts() in this case, are not supported." Para maiores informações vc pode consultar o site: http://mosesofegypt.net/post/2008/08/24/LINQ-to-Entities-what-is-not-supported.aspx   Cara, L2E é ruim demais... É muito engessado, e dependendo do tamanho do projeto, é altamente recomendado não usar L2E, use pelo menos L2Sql.   "LINQ to Entities only support Parameterless constructors and Initializers".     O que sugiro fazer, é voltar ao inicio de nossa conversa, faça uma sub-query mesmo.   Qualquer coisa, me avise ok? Mas de qualquer forma a questão o IN Query Sql foi resolvido, ne? Testei aqui e funcionou perfeitamente, como vc mesmo pode ver no projeto que me enviei.   Abraços e até mais. Aguardo seu retorno.   Att Luiz Maia
Responder

Gostei + 0

08/07/2009

Carlos Nogueira

Oi Luiz!

Então, antes de eu entrar em contato com vocês a respeito deste problema, já havia feito uma pesquisa para ver o que poderia encontrar de solução, e até havia encontrado uma que alguém desenvolveu um método que cria uma expressão para ser utilizada no método Where dos objetos do context. Só que antes de fazer alguns testes com isso que encontrei, decidi enviar o problema para vocês pois com o expertise que vocês possuem, talvez soubessem de algo mais simples ou mais eficaz.

Eu consegui solucionar o problema fazendo uso do linq to entities conforme o código que segue abaixo:

        private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
        {
            if (null == valueSelector)
            {
                throw new
                    ArgumentNullException("valueSelector");
            }
            if (null == values) { throw new ArgumentNullException("values"); }

            ParameterExpression p = valueSelector.Parameters.Single();
            if (!values.Any())
            {
                return e => false;
            }

            var equals = values.Select(value =>
            (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value,
            typeof(TValue))));
            var body = equals.Aggregate<Expression>((accumulate, equal) =>
            Expression.Or(accumulate, equal));
            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }

        public List<Pessoa> SelecionarDeficienteFisico(List<PessoaStatus> pPessoaStatus)
        {
            List<Pessoa> listPessoaDeficiencia = new List<Pessoa>();

            try
            {
                int[] _codStatusSelecao = ObterCodigosSelecao(pPessoaStatus);

                var consulta = from pessoa in (_codStatusSelecao[0] != 0) ? _context.tab_pessoa.Where((BuildContainsExpression<tab_pessoa, int>(item => item.tab_pessoa_status.id_status_pessoa, _codStatusSelecao))) : _context.tab_pessoa
                               where (pessoa.possui_deficiencia == "S")
                               && (pessoa.tab_tipo_contratacao.c_def_fisico == "X")
                               && (pessoa.tab_pessoa_status.id_status_pessoa != 2)
                               select new
                               {
                                   pessoa.Id_pessoa,
                                   pessoa.nome_completo,
                                   pessoa.matricula,
                                   pessoa.dt_admissao,
                                   pessoa.tab_tipo_deficiente_fisico.id_tipo_deficiencia,
                                   pessoa.tab_pessoa_status.id_status_pessoa,
                                   pessoa.tab_tipo_contratacao.Id_tipo_contratacao,
                                   pessoa.tab_organizacao.Id_organizacao
                               };

                var listConsulta = consulta.ToList();

                foreach (var list in listConsulta)
                {
                    Pessoa pessoa = new Pessoa();
                    pessoa.IdPessoa = list.Id_pessoa;
                    pessoa.NomeCompleto = list.nome_completo;
                    pessoa.Matricula = list.matricula;
                    pessoa.DtAdmissao = list.dt_admissao;
                    pessoa.TipoDeficienteFisico = new TipoDeficienteFisico(list.id_tipo_deficiencia);
                    pessoa.TipoContratacao=new TipoContratacao(list.Id_tipo_contratacao);
                    pessoa.Organizacao=new Organizacao(list.Id_organizacao);

                    listPessoaDeficiencia.Add(pessoa);
                }

                return listPessoaDeficiencia;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

Então, no que havia encontrado de pesquisa, o cara criou este método chamado BuildContainsExpression, que ao passar no método Where do objeto, permite eu simular a palavra-chave IN do SELECT no linq to entities. Então, acrescentei mais uma condição na sintaxe do linq para que quando o primeiro índice do array for igual a 0, desconsiderar o método Where. Isso é para aquilo que havia mencionado anteriormente (eu acho) de quando o usuário selecionar a opção todos, tentar resolver tudo em uma sintaxe só, ao invés de ficar fazendo if ou switch no código.

Se você quiser acessar o linq desta pesquisa para comentar o que pensa a respeito, é http://www.velocityreviews.com/forums/t645784-linq-where-clause.html

Agora, você comentou que conforme o porte do projeto, é altamente recomendável não utilizar o linq to entities. O que deu a entender, poderia usar o linq to sql e olha lá. Bem, você poderia me explicar melhor o do porque disso?
O local onde trabalho atualmente eles persistem os dados desta forma (entrei aqui a pouco tempo, 2 semana e pouco, e antes trabalhava persistindo os dados com o ADO.NET usando transact-sql nas classes, conforme política da outra empresa), mas conforme o que você me passar de informações ou referências sobre este assunto (fora o que você já mencionou a citação da equipe da Microsoft), posso tentar apresentar para a equipe. O projeto vai aumentar bastante de tamanho por aqui.

Fora este problema com o Contains (para simular o IN do SELECT) nas demais formas que precisei utilizar o linq to entities foi tranquilo e estou até achando interessante o aprendizado e a experiência que estou adquirindo com ele. O que você aconselharia nestes casos para persistir os dados, ou o que o mercado tem usado para persistir os dados? Linq to SQL, NHibernate, ADO.NET nas classes, store procedures no banco, .....

Fico no aguardo!

Atenciosamente,

Carlos

Responder

Gostei + 0

Utilizamos cookies para fornecer uma melhor experiência para nossos usuários, consulte nossa política de privacidade.

Aceitar