【问题标题】:Proper layout of functionality in a three layer architecture在三层架构中正确布局功能
【发布时间】:2013-05-28 18:15:44
【问题描述】:

我正在开发一个以 oracle 作为后端的 asp.net 项目。最初,我使用三层架构开发了这个应用程序,其中 UI 作为 aspx 页面,BLL 和 DAL 作为类库项目。我在 BLL 和 DAL 中都使用了静态类和方法。 DAL 仅由单个类组成,其静态方法为 Select、Execute 和 ExecuteScalar,它们接受 BLL 类转发的查询。

DAL

private static string connString = ""

private static OracleConnection conn;

public static OracleConnection OpenConn()
{
  if (conn==null)
  {
    conn = new OracleConnection(connString);
  }

  if (conn.State != ConnectionState.Open)
  {
    conn.Open();
  }

  return conn;
}

public static DataTable Select(string query)
{
  DataTable dt = new DataTable();
  OracleDataAdapter da = new OracleDataAdapter(query, OpenConn());
  da.Fill(dt);
  return dt;
}

public static void Execute(string query)
{
  OracleCommand cmd = new OracleCommand(query, OpenConn());
  cmd.ExecuteNonQuery();
}

public static int ExecuteScaler(string query)
{
  OracleCommand cmd = new OracleCommand(query, OpenConn());
  int id = Convert.ToInt32(cmd.ExecuteScalar());
  return id;
}

BLL 类调用这个 DAL 的方式如下 员工 BLL

public static DataTable GetEmployees(int facilityid)
    {
        DataTable dt = new DataTable();
        string q = string.Format("SELECT * FROM ..");
        dt = OraDAL.Select(q);
        return dt;
    }


    public static DataTable AddEmployee(string name, , int departmentid , int employeeType)
    {
        DataTable dt = new DataTable();
        string q = string.Format("INSERT INTO ...");
        dt = OraDAL.Select(q);
        return dt;
    }

现在我正在重构应用程序。具有 BLL 和 DAL 的三层架构与类库项目相同,带有一个名为 Domain 的附加类库项目,以包含所有其他项目引用的域类。我使用这些类在层之间传输数据。这次将 DAL 类保持为静态,将 BLL 保持为普通类。我已将大部分功能(查询)移至 DAL。现在 DAL 拥有EmployeesDAL、DepartmentsDAL 等类以及包含Select、Execute 和ExecuteScalar 静态方法的泛型类。

Employee.cs

public class Employee
{
    public int Id { get; set; }

    public string Name { get; set; }

    public Department department { get; set; }

    public EmployeeType employeeType { get; set; }
}

员工DAL

public static List<Employee> GetEmployees(int departmentid)
    {
        if (departmentid > 0)
        {
            string query = string.Format("SELECT * FROM EMPLOYEES WHERE department='{0}')", departmentid);
            return GetCollection(OraDAL.Select(query));
        }

        return null;
    }

    public static Employee GetEmployee(int employeeid)
    {
        if (employeeid > 0)
        {
            string query = string.Format("SELECT * FROM PMS_EMPLOYEES WHERE Id='{0}'", employeeid);
            return GetSingle(OraDAL.Select(query));
        }

        throw new Exception("Employee id not valid");
    }

    public static int AddEmployee(Employee employee)
    {
        if (employee !=  null)
        {
            string query = string.Format("INSERT INTO PMS_EMPLOYEES (name, department, employee_type) VALUES ('{0}','{1}','{2}')", employee.Name, employee.department.Id, employee.employeeType.Id);
            return OraDAL.Execute(query);
        }

        throw new Exception("Values not valid");
    }

private static List<Employee> GetCollection(DataTable table)
    {
        List<Employee> employees = null;
        if (table != null)
        {
            if (table.Rows.Count > 0)
            {
                employees = new List<Employee>();
                foreach (DataRow row in table.Rows)
                {
                    employees.Add(ReadDataRow(row));
                }
            }
        }
        return employees;
    }

    private static Employee GetSingle(DataTable table)
    {
        if (table != null)
        {
            if (table.Rows.Count > 0)
            {
                DataRow row = table.Rows[0];
                return ReadDataRow(row);
            }
        }

        return null;
    }

    private static Employee ReadDataRow(DataRow row)
    {
        Employee employee = new Employee() 
        {
            Id=int.Parse(row["ID"].ToString()),
            Name=row["NAME"].ToString(),
            employeeType=EmployeeTypesDAL.GetEmployeeType(int.Parse(row["EMPLOYEE_TYPE"].ToString())),
            department=DepartmentsDAL.GetDepartment(int.Parse(row["DEPARTMENT"].ToString()))
        };

        return employee;
    }

EmployeesBLL.cs

 public class EmployeesBLL
{
    public List<Employee> GetEmployees(int departmentid)
    {
        if (departmentid > 0)
        {
            return EmployeesDAL.GetEmployees(departmentid);
        }

        return null;
    }

    public int AddEmployee(Employee employee)
    {
        if (employee != null)
        {
            return EmployeesDAL.AddEmployee(employee);
        }

        throw new Exception("Employee cannot be null");
    }

帖子越来越长,但我希望您能更好地了解情况。 我在新设计中遇到了一些问题,让我问了这个问题,这是做事的正确方法吗?因为主要目标是避免在不同层之间来回移动数据表。虽然这种新的逻辑编码方式太费时,但值得付出努力。业务层变得太薄了,因为他们提供的唯一服务似乎是从 DAL 调用类似的方法,我首先需要 BLL 吗?如果是的话,什么情况下可能需要单独的 BLL。

编辑

我注意到上述设计的一个问题是数据库调用的数量。调用太多,因为当一个对象被填充时,所有子对象也被填充,导致对数据库的调用。例如填充 Employee 对象将导致填充部门实例。在大多数情况下,我只需要部门 ID 而不是整个部门的信息。

【问题讨论】:

  • 你的设计是数据驱动设计的一个很好的例子,是的——在它里面,业务逻辑只是对 crud 操作的包装。这真的很好,如果你的应用程序只做粗加工。顺便说一句,如果您打算让 BL 承担更多责任,请考虑 O/RM 方法,否则您将花费​​太多时间来支持您的数据访问层代码库。

标签: c# asp.net architecture n-tier-architecture


【解决方案1】:

三层架构各部分的目的如下:

1.界面

允许用户与您的域模型交互

2。领域模型/中间层

包含将您的特定领域或业务流程翻译成代码的内容。代表您的特定业务问题或环境。该层应包含应用程序的所有业务规则和业务对象。

3.数据访问层

允许您的域模型对象被持久化到数据存储并从中检索。数据访问层只是从持久数据存储(即数据库)中获取数据并将其转换为域模型对象,然后获取域模型对象并将它们持久化到数据存储中。

在这三个领域中,我认为迄今为止最重要的领域是领域模型/中间层。这是您的应用程序的核心和灵魂,也是应用程序实际解决特定业务问题并为将使用您的软件的组织提供价值的地方。其他两个区域的目的只是将您的域模型公开给用户(UI)或允许检索或存储它(数据层)。

这意味着您应该努力将尽可能多的精力花在域模型/中间层上。在这里,您可以解决组织的特定业务问题,并且可以真正为您的公司或客户增加价值。

在将此一般原则应用于您的特定情况时,我有以下 cmets/建议。

  1. 您正在手动创建数据访问层和手工制作 SQL 语句。一般来说,这是对您时间的非常低价值的使用。有许多 object-relation mapper (ORM) 工具,例如 nHibernate 或 entity framework,它们将负责从数据存储加载域模型对象并将对象持久化所需的低级管道到数据存储。我想你可能想调查这些。我认为使用这些 ORM 工具之一的解决方案将是比您建议的任何一个选项更好的解决方案。

  2. 根据您发布的代码,您的域模型/中间层只是一组属性,没有业务逻辑或业务规则。如果真的是这样,那就应该是这样。但是,最好利用您的时间与您的利益相关者交谈,并真正努力在域模型中为他们的流程建模,因为这是您可以最好地为您的组织增加价值的地方。

我知道这有点啰嗦,但希望对你有所帮助。

【讨论】:

  • 感谢 joe .. 我使用过 EF,但在尝试将其与 oracle 一起使用时遇到了一些问题。由于我不完全了解 EF 并且在不同的论坛上阅读过 ORM 有其自身的问题,所以我采用了这种方法。我想知道网络上是否有任何好的项目源代码,他们使用域对象但没有使用 ORM,因为 ORM 肯定不是唯一的选择。
  • 两种方式都做了(手工制作 DAL 与使用 ORM)我个人发现 ORM 可以节省大量时间,通过快速处理低级管道来节省我的痛苦让我把更多时间花在中间层,这对我的公司来说是最有价值的。我经常使用 nHibernate,我认为它与 oracle 配合得很好。但是,ORM 肯定有一个学习曲线,可能需要一些时间才能精通它们。此外,正如您所指出的,还有其他可用选项。
  • 谢谢 ..我肯定会尝试 nHibernate ..但我仍然想看看如何在不使用 ORM 的情况下以一种简洁的方式完成它。我希望找到任何这样的开源项目
【解决方案2】:

展示层:

你已经知道了。这是你的用户界面。它由视图、视图模型和(控制器/演示者)组成。此表示层仅具有与 UI 相关的逻辑(定位、css 样式等)。它不知道 BLL 的实现(只是接口),根本不知道 DAL(存储)。

业务层

您的业务逻辑所在的位置。每个选择操作、计算、验证、插入操作、删除、更新操作都属于这里。它不了解 UI,因此它可以在任何 UI 中工作,无论是 Web/桌面/移动应用程序,都具有相同的业务逻辑。这意味着在这一层中,没有使用明显的缓存/会话。相反,您可以将其包装在某个界面中。它不知道存储,这意味着你需要另一层来做存储,并且它是 DAL。

数据访问层

这一层只是简单地对存储(平面文件、数据库、Web 服务、xml、json 等)进行 CRUD 操作,并返回对象模型。就这样。它不知道 UI 也不知道业务规则。此层的目的是使其易于更换。假设您需要将存储从数据库替换为xml,这是层的工作,其他层保持不变。

回答您的问题

希望您已经知道 3 层的目的。

现在假设您使用 3 层和 BLL:

  1. 对员工存储的任何更改都不会影响 BLL 和 UI
  2. 对业务逻辑(新搜索参数)的任何更改都会影响 DAL
  3. 您的 BLL 不知道数据访问(连接/数据库上下文等),这意味着它根本不需要知道连接
  4. BLL 的其他明显映射

现在假设您使用的是没有 BLL 的 2 层:

  1. 对员工存储的任何更改都不会影响 UI(无 BLL)
  2. 对业务逻辑(新搜索参数)的任何更改都会影响 DAL
  3. 您的 UI 知道 DAL。如果您将其设计为单独的项目,则会添加参考并打破分层。这为您的 UI 提供了直接访问其他 DAL 的机会。

是的,在您的情况下使用 BLL 是没有用的,因为您只能将 UI 和 DAL 分开。但这就是三层的意义不是吗?

也就是说,如果您使用的是存储过程选择。您真正需要的是 DAL 中的通用存储库模式,这将在 BLL 访问期间为您提供很大帮助。

【讨论】:

  • 感谢 Fendy .. 你谈到了业务层中的验证,但我正在做的是验证代码背后 (UI) 中的输入,因为目标是 UI 应该发送整洁的对象(例如 Employee 对象)到业务层采取进一步行动。我要说的是,尽管每一层都有某种形式的验证,但 UI 不负责主要验证,因为这是捕获用户提供的数据的入口点。在根据这些数据构造对象之前验证这一层的用户输入不是更好吗?
  • 如果谈论数据类型(int 等),那么是的。无论如何,您都会将模型对象传递给业务逻辑。我在 BLL 中所说的验证是业务逻辑验证,例如:金钱不能低于 0,年龄不能低于 17,姓名长度不能低于 32,等等。毕竟,如果你想在 UI 级别重复验证,那没关系,但如果业务角色需要,添加到BLL中
  • 谢谢 .. 有道理.. 我同意应该在 BLL 中验证业务规则。
  • 我注意到上述设计的一个问题是数据库调用的数量。调用太多,因为当一个对象被填充时,所有子对象也被填充,导致调用数据库。例如填充 Employee 对象将导致填充部门实例。
  • 这取决于 ORM。我不太确定,但是像 EF 这样的 ORM 应该具有延迟加载机制,其中,只有在被访问时才检索 departement 实例。对于广义视图或急切加载(例如employee 内连接department),您可以使用sql 视图。 ORM 的一些缺点虽然是无法连接单独的数据库,但仍然使用 sql 视图来解决它。对于具有大量计算和临时表的报告数据结构,请改用存储过程。您可以为其创建自定义仓库
猜你喜欢
  • 2012-04-02
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 2023-03-25
  • 2013-06-06
  • 2012-02-27
相关资源
最近更新 更多