将try/catch放在3层架构中的何处

本文关键字:何处 3层 try catch 放在 | 更新日期: 2023-09-27 18:00:19

我有一个基于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或in UI层中使用它,或者我的代码是可以的。因为如果我在UI层中不使用try/catch,它就不会捕获异常(如果有)并显示错误页面。

将try/catch放在3层架构中的何处

异常处理和抛出一直被我合作过的大多数开发人员误解。

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

你可以也应该在任何地方使用try/catch/(finally),但是。。。

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

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

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

您还可以考虑将DataAccessException抽象化,并将更具体的异常(如SqlDataAccessException或SecurityDataAccessException)子类化。

正如您所知,当您希望确保某些代码在发生错误时运行时,即使您没有捕获和处理异常本身,也可以使用finally。在必须始终释放资源的情况下,try/finally将成为标准模式。

此外,在可能的情况下,将try/catch放在能够处理错误的最特定的代码补丁周围,允许周围代码中的编码错误导致应用程序崩溃。

你可能会想,"如果我还不知道会抛出哪些异常,我如何捕捉特定的异常?"你应该在ExecuteNonSql方法中看到它们的文档,这就是为什么用它抛出的异常来记录你自己的API/组件是如此重要。使用XML注释可以做到这一点,如果提供公共DLL,请打开XML注释文件生成器。

这可能看起来有很多需要接受的东西,但实际上并没有。当你投资于日志记录和正确的异常处理/抛出时,你将能够在几分钟内解决错误,你会觉得自己像冠军,你很快就会学会对其他人糟糕的代码感到不满:)

在您编程生涯的这个阶段,我强烈建议您阅读Cwalina和Abrams的框架设计指南。它将帮助你快速对所有这些类型的问题做出正确的选择,你会发现使用自己的代码和使用微软的API一样快乐(大部分)。

Luke

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

"无法{执行某些功能}。发生了{类型的异常}。{为错误提供补救建议或常见原因}。请参阅{内部异常|其他日志条目}。"

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

...
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);
}

这取决于,您可以在两个层中使用try catch块。

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

一般来说,

  1. 只捕获您可以处理的异常(在示例中捕获SqlException,而不是所有异常)

  2. 显示用户友好的消息(在您的示例中,简单地显示错误消息对用户来说没有意义)

  3. 记录异常

  4. 在异常发生的地方进行处理;如果是与DAL相关的异常,则在DAL层中对其进行处理;如果是与UI相关的异常则在UI层中对它进行处理。

您应该编写一个try-catch块来处理异常。不存在"总是把尝试接球放在这里或那里"这样的说法。我看到你这样处理异常:

catch (Exception ex)
{
    WebMsgBox.Show(ex.Message);
}

这很糟糕,原因有几个:

  1. 您捕获了通用Exception类型。几天前我看到一个关于这个的问题:https://stackoverflow.com/a/14727026/238682
  2. 您尝试在数据访问层中使用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
    }
}

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

如果您想通知用户错误状态,那么向上抛出异常。