C# 在3层体系结构中,在何处放置try/catch

C# 在3层体系结构中,在何处放置try/catch,c#,try-catch,3-tier,C#,Try Catch,3 Tier,我有一个基于3层的web应用程序。我想在我的业务逻辑层中使用try-catch块。在业务逻辑中使用try/catch块是正确的,还是我需要在UI层中使用它 请参阅我的DAL代码 Data Access Layer #region Insert in to Logbook public int Insert_LogBook(string Vehicle_Number, DateTime Vehicle_Booking_Date, TimeSpan Time_From, TimeSpan Time

我有一个基于3层的web应用程序。我想在我的业务逻辑层中使用try-catch块。在业务逻辑中使用try/catch块是正确的,还是我需要在UI层中使用它

请参阅我的DAL代码

Data Access Layer

#region Insert in to Logbook
public int Insert_LogBook(string Vehicle_Number, DateTime Vehicle_Booking_Date, TimeSpan Time_From, TimeSpan Time_To, int KM_Start, int KM_End, string Vehicle_Used_By, string Cost_Code, string Budget_Line, DateTime Entry_Date)
{
    try
    {
        SqlCommand com = new SqlCommand("Insert_LogBook", con);
        com.Parameters.Add("@Vehicle_Number", SqlDbType.NVarChar, 100).Value = Vehicle_Number;
        com.Parameters.Add("@Vehicle_Booking_Date", SqlDbType.DateTime).Value = Vehicle_Booking_Date;
        com.Parameters.Add("@Time_From", SqlDbType.Time).Value = Time_From;
        com.Parameters.Add("@Time_To", SqlDbType.Time).Value = Time_To;
        com.Parameters.Add("@KM_Start", SqlDbType.Int).Value = KM_Start;
        com.Parameters.Add("@KM_End", SqlDbType.Int).Value = KM_End;
        com.Parameters.Add("@Vehicle_Used_Byr", SqlDbType.VarChar, 100).Value = Vehicle_Used_By;
        com.Parameters.Add("@Cost_Code", SqlDbType.NVarChar, 50).Value = Cost_Code;
        com.Parameters.Add("@Budget_Line", SqlDbType.NVarChar, 50).Value = Budget_Line;
        com.Parameters.Add("@Entry_Date", SqlDbType.DateTime).Value = Entry_Date;
        con.Open();
        int res = com.ExecuteNonQuery();

    }
    catch (Exception ex)
    {
        WebMsgBox.Show(ex.Message);
    }
    finally
    {
        con.Close();
        con.Dispose();

    }
    return 1;
}
#endregion

所以我应该在bal或UI层中使用它,还是我的代码可以。因为如果我在UI层中不使用try/catch,它将不会捕获异常(如果有)并显示错误页面。

您应该将数据访问和业务逻辑放入类库中。 您不应该从BLL或DAL调用前端组件。 在BLL和DAL日志中创建日志记录,使用类似于log4net的方法将错误数据放在这里


如果要通知用户错误状态,则向上抛出异常。

您应该编写一个try-catch块,用于处理异常。没有“总是在这里或那里试抓”这样的事情。 我看到您这样处理异常:

catch (Exception ex)
{
    WebMsgBox.Show(ex.Message);
}
这是不好的,原因有几个:

  • 您将捕获泛型
    异常
    类型。几天前我看到一个关于这个的问题:
  • 您尝试在数据访问层内使用
    WebMsgBox.Show
    处理异常,这打破了层的边界
  • 该示例的另一个问题是一个小问题,但我认为它在长期(总体代码设计)中很重要。 您应该将错误处理逻辑与实际应用程序逻辑分开。因此,当您使用try-catch块时,请尽量减少其中的逻辑,以便代码变得更可读

    public int Insert_LogBook(string Vehicle_Number, DateTime Vehicle_Booking_Date, TimeSpan Time_From, TimeSpan Time_To, int KM_Start, int KM_End, string Vehicle_Used_By, string Cost_Code, string Budget_Line, DateTime Entry_Date)
    {
        using(SqlCommand com = new SqlCommand("Insert_LogBook", con))
        {
            com.Parameters.Add("@Vehicle_Number", SqlDbType.NVarChar, 100).Value = Vehicle_Number;
            com.Parameters.Add("@Vehicle_Booking_Date", SqlDbType.DateTime).Value = Vehicle_Booking_Date;
            com.Parameters.Add("@Time_From", SqlDbType.Time).Value = Time_From;
            com.Parameters.Add("@Time_To", SqlDbType.Time).Value = Time_To;
            com.Parameters.Add("@KM_Start", SqlDbType.Int).Value = KM_Start;
            com.Parameters.Add("@KM_End", SqlDbType.Int).Value = KM_End;
            com.Parameters.Add("@Vehicle_Used_Byr", SqlDbType.VarChar, 100).Value = Vehicle_Used_By;
            com.Parameters.Add("@Cost_Code", SqlDbType.NVarChar, 50).Value = Cost_Code;
            com.Parameters.Add("@Budget_Line", SqlDbType.NVarChar, 50).Value = Budget_Line;
            com.Parameters.Add("@Entry_Date", SqlDbType.DateTime).Value = Entry_Date;
            con.Open();
            int res = com.ExecuteNonQuery();
    
            return 1;
        }
    
    }
    
    public void SomeMethodWhichUsesThatInsert()
    {
        try
        {
            //call Insert_LogBook
        }
        catch(SomeException e)
        {
            //handle
        }
    
    }
    

    这取决于具体情况,您可以在两个层中使用
    try-catch

    但问题不在于您在哪里使用异常处理代码;问题是你如何使用。在您提供的示例中,您正在捕获一个通用的
    异常
    ,您不知道它是SqlException还是任何其他异常

    总的来说

  • 仅捕获您可以处理的异常(在示例catch
    SqlException
    中,不是所有异常)

  • 显示用户友好的消息(在您的示例中,仅显示错误消息对用户没有意义)

  • 记录异常

  • 处理发生异常的地方;如果是DAL相关异常,则在DAL层中处理它,如果是UI相关异常,则在UI层中处理它


  • 异常处理和抛出是我一直认为被我合作过的大多数开发人员误解的事情

    • 异常允许您在代码中查找bug
    • 他们停止运行程序,以防止对业务造成“伤害”
    • 它们允许您在合理预期的异常(移动应用程序上的网络不可用)和意外错误异常(如NullReferenceException)之间进行过滤
    • 他们直截了当地谈到了发生错误的原因
    • 它们允许每个组件添加一层关于上下文和状态的信息来帮助调试,这就是catch、wrap和throw模式
    你可以也应该在任何地方使用try/catch/(finally),但是

    只有当您知道哪些异常可能发生并且可以从中恢复时,才使用catch。您应该很少捕获基本异常类型。允许程序员/测试人员/用户发现所有其他错误

    如果要引发另一个异常并将原始异常附加为InnerException,则可能需要捕获基本异常类型

    在创建自己的异常类型时,您不应该三思而后行或懒散。例如,您可能希望编写一个DataAccessException并抛出该异常,同时在InnerException中附加的层中捕获异常。这样,您的日志记录将记录一个异常类型,该类型更准确地显示错误发生的位置,调用代码只能选择捕获DataAccessException并执行重试或其他操作

    您还可以考虑对DATAAccess异常进行抽象和子类化,如Sql DATAAccess异常或SualDeDATAccess异常。 如您所知,当您希望确保在发生错误时运行某些代码时,即使您没有捕获并处理异常本身,也可以使用finally。在必须始终释放资源的情况下,try/finally成为标准模式

    此外,如果可能的话,将try/catch放在最具体的代码补丁周围,您可以在那里处理错误,从而允许周围代码中的编码错误使应用程序崩溃

    您可能会想,“如果我还不知道将抛出哪些异常,如何捕获特定异常?”您应该看到它们记录在ExecuteOnSQL方法中,这就是为什么记录自己的API/组件及其抛出的异常非常重要的原因。使用XML注释执行此操作,如果提供公共DLL,请打开XML注释文件生成器

    这可能看起来很难理解,但实际上并不是这样。当您投资于日志记录和适当的异常处理/抛出时,您将能够在几分钟内解决bug,您将感觉自己是champ,并且很快就会对其他人糟糕的代码感到不安:)

    在您编程生涯的这个阶段,我强烈建议您阅读Cwalina和Abrams的框架设计指南。它将帮助您在所有这些类型的问题上快速做出正确的选择,您会发现使用自己的代码和使用Microsoft的API(大多数情况下)一样令人愉快

    卢克

    添加一些关于消息的信息。我在错误消息中使用了这类东西

    无法{执行某些函数}。发生了{异常类型}。{提供补救建议或错误的常见原因}。请参阅{内部异常|进一步的日志条目}

    例如,在应用程序中自动保存状态的组件中:

    ...
    catch(FileNotFoundException fnfe)
    {
        string m = String.Format("Cannot save changes. A FileNotFoundException occurred. Check the path '{0}' is valid, that your network is up, and any removable media is available. Please see inner exception.", path);
    
        _log.Error(m, fnfe);
    
        throw new StorageLifecycleException(m, fnfe);
    }
    

    你能详细说明一下我的代码吗?我没有在3层+1中使用这个错误