n层架构:存储业务对象的最佳位置?

4
假设我有一个3层架构(UI、业务和数据)。通常,我会创建第四个名为“Model”或“Common”的项目来保存我的数据访问对象,然后其他每个项目都会使用该项目。
现在,我正在开发一个项目,其中一些数据访问对象具有像Save()等方法,需要访问Data项目。因此,如果我试图在Data项目中使用Model/Common项目,就会出现循环引用的问题。
在这种情况下,最好将数据访问对象放在哪里?我可以将其保留在Data项目本身中,但是我的UI项目需要知道数据访问对象,需要访问Data层,这不是很好。

我应该澄清一下,我所说的业务对象实际上是数据访问对象——这些类具有与我的数据库表中字段相关的属性。 - Prabhu
小心不要混淆“层”和“层”的术语。听起来你所说的是层,而不是层级。 - RickNZ
7个回答

8
我认为您的n层结构可能有些问题。听起来您正在构建更多的两层系统。
在一个真正的三层项目中,只有数据层可以与数据库交互。您已经使用了“Model”或“Common”项目来实现这一点。这些项目是您的数据层。但是,您偏离了正确的方向,因为只有业务层才应该允许与它们通信。您的演示代码根本不应该与数据层项目通信。
当您有超过3个“层”时,n层就出现了,但是相同的原则适用:每个层只知道如何使用(并且只需要参考)其下面的层,并为其上面的层提供api。在我的项目中,我采用您典型的演示、业务和数据层,并在业务和数据之间提供第四个“翻译”层。这样,数据层可以返回通用类型,如数据集、数据表和数据行,而业务层只需使用强类型的业务对象。翻译层仅在通用数据对象和强类型对象之间进行转换。这样,对传统层之一的更改不太可能需要更改另一层。

好的观点,但是你需要在第二段最后一句话中加上一个“不”。 - jason
实际上,我的模型从不与数据库交互。只有现在我接手的项目中,自定义对象具有数据访问方法,因此我才会问这个问题。顺便问一下,你为什么需要翻译层呢?难道你不能直接让业务层使用通用对象吗? - Prabhu

2
这是我项目中的内容。
1.) Application.Infrastructure 所有业务对象、业务对象集合、数据访问类及其自定义属性和扩展方法的基类,以及通用验证框架。这决定了我最终 .net 应用程序的整体行为组织。
2.) Application.DataModel
用于数据库的类型化数据集。
扩展了 TableAdapters 以纳入事务和其他我可能需要的功能。
3.) Application.DataAccess
数据访问类。
实际上查询数据库操作的地方,使用底层的类型化数据集。
4.) Application.DomainObjects
业务对象和业务对象集合。
枚举。
5.) Application.BusinessLayer 提供从表现层可访问的管理器类。
HttpHandlers。
我的自定义页面基类。
更多东西在这里...
6.) Application.WebClient 或 Application.WindowsClient
我的表现层。
引用 Application.BusinessLayer 和 Application.BusinessObjects。
Application.BusinessObjects 在整个应用程序中都被使用,只要需要 [除了 Application.DataModel 和 Application.Infrastructure],它们就会遍历所有层。
我的所有查询仅在 Application.DataModel 中定义。
Application.DataAccess 作为任何数据访问操作的一部分返回或接受业务对象。使用反射属性创建业务对象。每个业务对象都标记有映射到目标数据库表的属性,并且业务对象内的属性都标记有映射到相应数据库表列的属性。
我的验证框架让我可以使用指定的 ValidationAttribute 验证每个字段。
我的框架大量使用属性来自动化大部分繁琐的任务,如映射和验证。我还可以将新功能作为框架中的新方面添加。
在我的应用程序中,样例业务对象看起来像这样。
User.cs
[TableMapping("Users")]
public class User : EntityBase
{
    #region Constructor(s)
    public AppUser()
    {
        BookCollection = new BookCollection();
    }
    #endregion

    #region Properties

    #region Default Properties - Direct Field Mapping using DataFieldMappingAttribute

    private System.Int32 _UserId;

    private System.String _FirstName;
    private System.String _LastName;
    private System.String _UserName;
    private System.Boolean _IsActive;

    [DataFieldMapping("UserID")]
    [DataObjectFieldAttribute(true, true, false)]
    [NotNullOrEmpty(Message = "UserID From Users Table Is Required.")]
    public override int Id
    {
        get
        {
            return _UserId;
        }
        set
        {
            _UserId = value;
        }
    }

    [DataFieldMapping("UserName")]
    [Searchable]
    [NotNullOrEmpty(Message = "Username Is Required.")]
    public string UserName
    {
        get
        {
            return _UserName;
        }
        set
        {
            _UserName = value;
        }
    }

    [DataFieldMapping("FirstName")]
    [Searchable]
    public string FirstName
    {
        get
        {
            return _FirstName;
        }
        set
        {
            _FirstName = value;
        }
    }

    [DataFieldMapping("LastName")]
    [Searchable]
    public string LastName
    {
        get
        {
            return _LastName;
        }
        set
        {
            _LastName = value;
        }
    }

    [DataFieldMapping("IsActive")]
    public bool IsActive
    {
        get
        {
            return _IsActive;
        }
        set
        {
            _IsActive = value;
        }
    }

    #region One-To-Many Mappings
    public BookCollection Books { get; set; }

    #endregion

    #region Derived Properties
    public string FullName { get { return this.FirstName + " " + this.LastName; } }

    #endregion

    #endregion

    public override bool Validate()
    {
        bool baseValid = base.Validate();
        bool localValid = Books.Validate();
        return baseValid && localValid;
    }
}

BookCollection.cs

/// <summary>
/// The BookCollection class is designed to work with lists of instances of Book.
/// </summary>
public class BookCollection : EntityCollectionBase<Book>
{
    /// <summary>
    /// Initializes a new instance of the BookCollection class.
    /// </summary>
    public BookCollection()
    {
    }

    /// <summary>
    /// Initializes a new instance of the BookCollection class.
    /// </summary>
    public BookCollection (IList<Book> initialList)
        : base(initialList)
    {
    }
}

1

如果您使用关系型后端,数据层应该以行和列的形式存储信息(如果您喜欢,可以使用类型化的数据集),不要使用“业务对象”。

业务层应该使用您的“业务对象”。它可以引用BusinessObjects项目。

总之:

  • UI引用Business和BusinessObjects
  • Business引用BusinessObjects和Data

希望这有所帮助。


1

我有一个BusinessObjects项目,服务器端存储映射(ORM),并提供相应的DataAccess服务,公开CRUD操作(以及其他像GetAll等操作)。


0
我建议在模型项目中创建一个接口,定义你想要的内容,并在数据层实现该定义。这样,所有三个(或四个)项目都可以使用该定义,而不必知道它是如何实现的。

0
在我看来,只有业务层应该了解数据访问对象。它应该在执行自己的业务规则和逻辑的同时使用它们进行数据操作,然后将哑对象(例如数据传输对象)返回给上面的 UI 层。
你可以使用类似 AutoMapper 这样的工具来自动映射你的数据和业务对象之间的关系。

0
这真的取决于模式,如果你正在使用MVC(前端控制器模式),那么模型就是应用程序操作的特定领域数据的表示(通常是ORM帮助处理此类数据)。我们使用一个DATA项目来存放这些类。
模型不是数据访问对象,所以数据访问以仓储的形式存在于另一个项目中。业务规则使用服务层,最后是Web项目。在这种方法中,Data.dll在所有项目中被引用。模型就像无处不在一样。
DATA(Domain Model) -> REPOSITORY(Data Access) -> SERVICE(Business Rules) -> WEB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接