none
Insert com Integridade Referencial - Como traduzir erro gerado pela procedure RRS feed

  • Pergunta

  • Ola.

    Estou postando minha dúvida aqui, conf conselho do pessoal lá do fórum SQL.

    Eu tenho duas tabelas que se relacionam entre sim através de chave estrangeira

    Tabela Equipamentos  = IdPatrimonio(PK)
    Tabela Ordens = IdOrdem (PK), IdPatrimonio(FK)

    O que eu quero, é que no momento de inserir uma ordem, caso o IdPatrimonio não exista na tabela de Equipamentos, emita uma mensagem de erro "equipamento não cadastrado" para o usuário, ao invés de simplesmente gerar uma excessão pela quebra de integridade referencial.


    A principio eu pensei em fazer essa verificação na procedure, o que foi DESACONSELHADO pelo pessoal de SQL.
    Eles me passaram o seguinte:

    "Enquanto o banco de dados gasta recursos formatando mensagens, consultas poderiam rodar mais rapidamente"


    Depois pensei em fazer a classe fazer a verificação, o q tb foi DESACONSELHADO, me passaram o seguinte:

    "Não acho que sua classe tenha que fazer a verificação (até porque nesse caso seriam dois acessos ao banco (um para consultar e um para cadastrar)).
    Acho que sua classe deve simplesmente chamar a SP e caso a SP retorne um erro, a classe deve traduzir a mensagem."

    Só que eu não sei ao certo como "traduzir" essa mensagem.
    Deixo abaixo meu código de Insert no Banco e o código da procedure.

    private String ConStr = System.Configuration.ConfigurationManager.ConnectionStrings["DINFConnectionString"].ConnectionString;
    public void Insert(DTO.OrdemServico ordem)
    {
        SqlConnection con = new SqlConnection(ConStr);
        string SQL = "InsertOrdensServico";
        SqlCommand cmd = new SqlCommand(SQL, con);
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@IdOrdem", ordem.IdOrdem);
        cmd.Parameters.AddWithValue("@IdPatrimonio", ordem.IdPatrimonio);        
        con.Open();
        try
        {
            cmd.ExecuteNonQuery();
        }
        finally
        {
            con.Close();
        }
    }


    Procedure


    ALTER PROCEDURE dbo.InsertOrdensServico
    @IdOrdem int, @IdPatrimonio int
    AS
    BEGIN
    	INSERT into OrdensServico VALUES
    	(@IdPatrimonio)
    END	

    Obrigado




    • Editado Douglas Luiz quarta-feira, 8 de julho de 2009 19:53
    quarta-feira, 8 de julho de 2009 19:31

Respostas

  • Douglas,

    Eu não sabia que você estava utilizando ObjectDataSource. Tente modificar o seu evento para:

    protected void ObjectDataSource1_Inserted(object sender, ObjectDataSourceStatusEventArgs e)
    {
        if (e.Exception != null)
        {
            if (e.Exception.InnerException is SqlException)
            {
                Label1.Text = e.Exception.InnerException.Message;           
                e.ExceptionHandled = true;         
            }
        }
    }


    Veja esse link:
    http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.deleted.aspx

    Att.

    Ari C. Raimundo
    • Marcado como Resposta Douglas Luiz sexta-feira, 17 de julho de 2009 18:54
    sexta-feira, 17 de julho de 2009 17:46

Todas as Respostas

  • Você pode simplesmente fazer o seguinte:

    private String ConStr = System.Configuration.ConfigurationManager.ConnectionStrings["DINFConnectionString"].ConnectionString;
    public void Insert(DTO.OrdemServico ordem)
    {
        SqlConnection con = new SqlConnection(ConStr);
        string SQL = "InsertOrdensServico";
        SqlCommand cmd = new SqlCommand(SQL, con);
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@IdOrdem", ordem.IdOrdem);
        cmd.Parameters.AddWithValue("@IdPatrimonio", ordem.IdPatrimonio);        
        con.Open();
        try
        {
            cmd.ExecuteNonQuery();
        }
        catch(SqlException e)
    
    
        {
    
    
            MessageBox.Show("Equipamento não cadastrado.");
    
    
        }
    
    
        finally
        {
            con.Close();
        }
    }
    
    
    Vc pode refinar esse catch analisando a propriedade e.Errors pra criar mensagens mais detalhadas dependendo da sua necessidade.

    Entretanto, eu, pessoalmente, consultaria no banco antes de tentar inserir.. Não acho uma boa prática assumir que um erro seja parte da lógica de funcionamento de um sistema. A consulta a mais no banco deixa seu codigo mais inteligente, legivel e... bem, não fica um código gambiarrizado.

    Abraços
    quarta-feira, 8 de julho de 2009 23:47
  • Ola Pedro, valeu pela dica.

    Eu acho que esqueci de mencionar, o projeto é web e não windows, se não me engano o messagebox é para windows não?? para web teria algum comando que fizesse a mesma coisa?? ou somente a tag <script>?

    QUanto ao fato de vc achar melhor consultar primeiro no banco, para depois inserir ..... bom ai fico na dúvida, pois no fórum de sql o pessoal me desanconselhou fazer duas consultas ao banco, uma para consultar na tabela equipamentos, e outra para inserir na tabela ordens.

    Mas em todo caso, na tua opinião qual seria  a melhor forma para atender meu caso??? pois a unica coisa que eu quero é que ao tentar abrir ordem para um equipamento não cadastrado, o usuario receba um alerta na tela, indicando que o equipamento nao esta cadastrado.

    Outra dúvida que me surgiu, sera q não ficaria mais elegante fazer essa verificação na camada de negócios?? pois o codigo acima é da minha camada DAL, e acho q nao passei o codigo da BLL segue abaixo:

    public void Insert(DTO.OrdemServico ordem)
            {
                DAL.OrdensServico DalOrdens = new DAL.OrdensServico();
                DalOrdens.Insert(ordem);
            }


    valeu
    quinta-feira, 9 de julho de 2009 11:34
  • Douglas,

    Dependendo do tamanho da aplicação, apesar de não ser a melhor prática, não vejo problemas em fazer a verificação na própria procedure.

    IF NOT EXISTS (SELECT IdPatrimonio FROM Equipamentos WHERE IdPatrimonio = @IdPatrimonio)
    BEGIN

        RAISERROR(N'Equipamento não cadastrado !', 16, 1)
        RETURN

    END

    A solução do Pedro em verificar a propriedade e.Errors também é válida e que acredito ser a melhor opção.

    Att.

    Ari C. Raimundo
    sexta-feira, 10 de julho de 2009 01:03
  • Oi Ari.

    Então, a procedure com o RAISERROR eu até tenho funcionando aqui.
    Mas não sei como levantar a mensagem do RAISERROR para o cliente.
    Na minha DAL eu tenho o método de inserção que chama a procedure, mas se tento incluir um equipamento não cadastrado ele não me mostra a mensagem la do RAISERRO.

    Assim como tb não sei levantar a mensagem gerado pelo bloco catch com a excessao sql.

    Vc pode me ajudar??

    Meu projeto é web em camadas, onde tenho as seguintes camadas

    BD--->DTO--->DAL--->BLL--->Interface Web

    eu colocando o erro no bloco catch do método de inserção da camada DAL, depois como eu faço para mostrar ele para o cliente qdo digitar um patrimonio nao cadastrado??

    QUal seria o comando equivalente em Web para o MessageBox.Show???
    sexta-feira, 10 de julho de 2009 11:12
  • Douglas,

    Você tem a opção de tratar o SqlException no DALC ou na interface. A mensagem de erro (e.Message) é aquela indicada no RAISERROR da procedure (pode conferir).

    Quanto ao comando equivalente ao MessageBox.Show existem também diversas opções:

    1 - Utilizar a função alert do JavaScript (não recomendo).

    2 - Criar um Label com a propriedade Visibile = false e quando ocorrer o erro modificar essa propriedade e também o texto a ser mostrado (o texto referente ao erro).

    3 - Utilizar o ModalPopupExtender do AJAX Control Toolkit.
    http://www.asp.net/AJAX/AjaxControlToolkit/Samples/ModalPopup/ModalPopup.aspx
    http://msdn.microsoft.com/en-us/magazine/cc164247.aspx

    Espero ter ajudado.

    Att.

    Ari C. Raimundo
    sábado, 11 de julho de 2009 00:39
  • Ari,

    estou sem o projeto aqui agora, acabou ficando no trabalho e estou em casa.

    Mas seria correto eu utilizar assim:

    catch(SqlException e)
    {
        e.Message("Equipamento não cadastrado.");
    }
    
    
    
    lá na minha classe DAL no método de insert??

    Veja q eu tive q escrever "Equipamento não cadastrado", como eu faria para buscar a mensagem lá da procedure do RAISERROR?
    Isso q eu não estou conseguindo, pois na interface eu estou digitando um equipamento que não existe de propósito, e ele tá me dando excessão no debug, não ta exibindo a mensagem de erro cadastrada na procedure,

    Se vc puder me ajudar com um exemplo de código, pode ser na DAL mesmo nesse método insert, como eu faria pra ele puxar o mensagem do raiserror.


    abraço
    sábado, 11 de julho de 2009 13:52
  • Douglas,

    A propriedade Message (não é um método) já possui a mensagem que você escreveu na procedure. O máximo que você pode fazer nessa camada é gerar uma nova exception.

    throw new SuaException(e.Message);

    E então pegar essa exception em outra camada com try/catch.

    try {}
    catch (SuaException ex)
    {
    // mostre a mensagem de erro
    }

    Att.

    Ari C. Raimundo

    sábado, 11 de julho de 2009 21:13
  • Ari, vc escreveu:

    "A propriedade Message  já possui a mensagem que você escreveu na procedure. "

    é justamente a mensagem que eu escrevi na procedure que eu não consigo mostrar quando cadastro um equipamento não existente por exemplo.

    vc pode me ajudar? qual comando eu utilizo para mostrar a mensagem de erro escrita lá na procedure??

    eu imagino que seja:

    try {comandos de insert }
    catch (sqlexception e)
    {
          // aqui eu não sei o que colocar para exibir a mensagem de erro lá da procedure
    }



    obrigado

    Douglas
    segunda-feira, 13 de julho de 2009 11:51
  • Ari, Estou tentando mas tá faltando alguma coisa. Segue meu código quem sabe vc não me ajuda a dizer onde está errado:

    DAL Método Insert

    public void InsertRaiseError(DTO.Ordem ordem)
    {
        comandos de inserção
        try
        {
             cmd.ExecuteNonQuery();
         }
        catch (SqlException sqlex)
        {
            throw sqlex;
         }
        catch (Exception ex)
        {
            throw ex;
        }
         finally
        {
            con.Close();
        }
    }

    BLL Chamando Método Insert

    public void InsertRaiseErros(DTO.Ordem ordem)
    {
        try
        {
            DAL.Ordens DalOrdens = new DAL.Ordens();
            DalOrdens.InsertRaiseError(ordem);
        }
        catch (SqlException sqlex)
        {
            throw sqlex;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    Agora é que pega, na Página aspx eu to tentando de Duas formas, nessa primeira logo abaixo, o label me retorna o seguinte: "Uma exceção foi acionada pelo destino de uma chamada" , ao invés de me retornar a mensagem lá da procedure que seria "Equipamento não cadastrado".

    protected void ObjectDataSource1_Inserted(object sender, ObjectDataSourceStatusEventArgs e)
        {
            if ((e.Exception != null))
            {
              Label1.Text = e.Exception.Message;            
              e.ExceptionHandled = true;          
            } 
    }

    E na segunda  abaixo me retorna a página de erros amarela, padrão do .Net.
    protected void ObjectDataSource1_Inserted(object sender, ObjectDataSourceStatusEventArgs e)
        {
              try { }
              catch (SqlException ex)
              {   
                    Label1.Text = ex.Message;
                    e.ExceptionHandled = true;            
              }
        }

    Segue a mensagem, note que ele me diz q a mensagem é EQUIPAMENTO NÃO CADASTRADO.
    Como eu faço para mostrar essa mensagem para o usuário, atribui-la no meu Label


    Erro de Servidor no Aplicativo '/Website'.
    --------------------------------------------------------------------------------

    Equipamento não cadastrado
    Descrição: Ocorreu uma exceção não tratada durante a execução da atual solicitação da Web. Examine o rastreamento de pilha para obter mais informações sobre o erro e onde foi originado no código.

    Detalhes da Exceção: System.Data.SqlClient.SqlException: Equipamento não cadastrado

    Erro de Origem:


    Linha 49:             catch (SqlException sqlex)
    Linha 50:             {
    Linha 51:                 throw sqlex;
    Linha 52:                
    Linha 53:             }
     

    Arquivo de Origem: D:\Douglas Compartilhamento\Projetos Visual Studio\Ordens\Solution1\BLL\Ordens.cs    Linha: 51

    Rastreamento de Pilha:


    [SqlException (0x80131904): Equipamento não cadastrado]
       BLL.Ordens.InsertRaiseErros(Ordem ordem) in D:\Douglas Compartilhamento\Projetos Visual Studio\Ordens\Solution1\BLL\Ordens.cs:51

    ...continua o rastreamento da pilha abaixo, mas eu nem copiei tudo.


     





    • Editado Douglas Luiz segunda-feira, 13 de julho de 2009 12:58
    segunda-feira, 13 de julho de 2009 12:56
  • Douglas Luiz,

    Provavelmente a melhor forma que tens de tratar erros de Stored Procedures de SQL, é implementares um mecanismo de Log no teu projecto.

    Por exemplo:

            protected bool AdicionarDados(object dados)
            {
                try
                {
                    //a inserção de dados no SQL
                }
                catch (Exception ex)
                {
                    //aqui inseres o "ex.message" numa tabela de log da Base de Dados.
                    //assim podes criar uma página na tua aplicação somente para visualizares os erros das aplicação
                }
                finally
                {
                    //fechar a ligação à base de dados
                }
            }

    Bruno Pires - www.blastersystems.com/blog
    • Sugerido como Resposta Bruno AC Pires quarta-feira, 15 de julho de 2009 16:14
    quarta-feira, 15 de julho de 2009 16:11
  • Bruno

    eu quero apresentar a mensagem para o usuário,  ao tentar inserir um patrimonio não existente, apresentar a mensagem de equipamento não cadastrado para ele.
    quinta-feira, 16 de julho de 2009 13:06
  • Douglas,

    Eu não sabia que você estava utilizando ObjectDataSource. Tente modificar o seu evento para:

    protected void ObjectDataSource1_Inserted(object sender, ObjectDataSourceStatusEventArgs e)
    {
        if (e.Exception != null)
        {
            if (e.Exception.InnerException is SqlException)
            {
                Label1.Text = e.Exception.InnerException.Message;           
                e.ExceptionHandled = true;         
            }
        }
    }


    Veja esse link:
    http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.deleted.aspx

    Att.

    Ari C. Raimundo
    • Marcado como Resposta Douglas Luiz sexta-feira, 17 de julho de 2009 18:54
    sexta-feira, 17 de julho de 2009 17:46