【问题标题】:ORM and layersORM 和层
【发布时间】:2009-05-28 22:24:41
【问题描述】:

很抱歉,这里到处都是……但我觉得自己就像一条追着我尾巴的狗,我现在都很困惑。

我正在尝试以最简洁的方式开发 3 层解决方案(IL、BL、DL),其中 DL 使用 ORM 来抽象对数据库的访问。

我所见过的任何地方,人们都使用 LinqToSQL 或 LLBLGen Pro 来生成代表 DB 表的对象,并在所有 3 层中引用这些类。 似乎 40 年的编码模式被忽略了——或者发生了范式转变,我错过了为什么这样做完全可以的解释部分。

然而,希望成为数据存储机制不可知论似乎仍有一些基础——看看 LinqToSQL 发生了什么:针对它编写了很多代码——仅适用于 MS 放弃它......所以我想尽可能地隔离 ORM 部分,只是不知道如何。

所以,回到绝对基础,以下是我希望以非常干净的方式组装的基本部分:

我开始的程序集: UL.dll BL.dll DL.dll

主要类:

具有公开 MessageAddress 对象集合(称为 MessageAddresses)的属性的 Message 类:

class Message 
{
    public MessageAddress From {get;}
    public MessageAddresses To {get;}
}

每层的功能:

BL 向 UI 公开一个名为 GetMessage (Guid id) 的方法,该方法返回一个 Message 实例。

BL 依次包裹 DL。

DL 有一个包装了 Provider 实例的 ProviderFactory。 DL.ProviderFactory 公开(可能是我的问题的一部分)两个静态方法,称为 GetMessage(Guid id),和 SaveMessage(消息消息) 最终目标是能够将一个为 Linq2SQL 编写的提供程序替换为一个为 LLBLGen Pro 编写的提供程序,或者另一个不针对 ORM 工作的提供程序(例如 VistaDB)。

设计目标: 我想要层分离。 我希望每一层只依赖于它下面的层,而不是它上面的层。 我希望 ORM 生成的类仅在 DL 层中。 我希望 UL 与 BL 共享 Message 类。

因此,这是否意味着:

a) 消息在 BL 中定义 b) DB 表的 Db/Orm/Manual 表示(“DbMessageRecord”或“MessageEntity”,或其他 ORM 调用它)在 DL 中定义。 c) BL 依赖于 DL d) 在调用没有 ref 或不知道 BL 的 DL 方法之前,BL 必须将它们转换为 BL 实体(例如:DbMessageRecord)?

UL:

Main() 
{
    id = 1;
    Message m = BL.GetMessage(id);
    Console.Write (string.Format("{0} to {1} recipients...", m.From, m.To.Count));
}

BL:

static class MessageService 
{ 
    public static Message GetMessage(id)
    {
        DbMessageRecord message = DLManager.GetMessage(id);
        DbMessageAddressRecord[] messageAddresses = DLManager.GetMessageAddresses(id);

        return MapMessage(message, 
    }

    protected static Message MapMessage(DbMessageRecord dbMessage. DbMessageAddressRecord[] dbAddresses)
    {
        Message m = new Message(dbMessage.From);
        foreach(DbMessageAddressRecord dbAddressRecord in dbAddresses){
        m.To.Add(new MessageAddress (dbAddressRecord.Name, dbAddressRecord.Address);
    }
}

DL:

static class MessageManager 
{
    public static DbMessageRecord GetMessage(id);
    public static DbMessageAddressRecord  GetMessageAddresses(id);
}

问题: a) 显然,这迟早要做很多工作。 b) 更多错误 c) 较慢 d) 由于 BL 现在依赖于 DL,并且正在引用 DL 中的类(例如 DbMessageRecord),因此似乎由于这些是由 ORM 定义的,因此您不能删除一个提供程序,并用另一个提供程序替换它,...使整个练习变得毫无意义……还不如通过 BL 使用 ORM 的类。 e) 或者...在 BL 和 DL 之间需要另一个组件,并且需要另一个映射以使 BL 独立于底层 DL 类。

希望我能更清楚地提出问题......但我真的只是在这一点上迷路了。任何帮助将不胜感激。

【问题讨论】:

  • 如果发生了范式转变,要么是人们意识到数据传输对象不是很干燥,要么可能是馄饨代码正在成为比千层面代码更流行的结构。
  • 我认为千层面代码是使软件可扩展的唯一方法(我个人认为我不会知道,因为我不必处理这样的规模)。您是说 Ravioli Code + WCF 可以实现相同的效果,因此 Lasagna 代码层分离不再那么重要?无论如何——看不到如何使馄饨代码跨多个 ORM 提供程序模块化。似乎需要某种映射。顺便说一句,你让我饿了。

标签: c# design-patterns orm


【解决方案1】:

这有点到处都是,让我想起了我第一次涉足 orm 和 DDD。 我个人使用核心域对象、消息传递对象、消息处理程序和存储库。 因此,我的 UI 向处理程序发送一条消息,该处理程序又通过存储库对我的对象进行水合,并在该域对象中执行业务逻辑。我使用 NHibernate 进行数据访问,使用 FluentNHibernate 进行类型化绑定,而不是使用松散的 .hbm 配置。

所以消息传递是我的 UI 和我的处理程序之间共享的所有内容,并且所有 BL 都在域上。

我知道我可能会为自己的解释敞开心扉接受惩罚,如果不清楚,我稍后会辩护。

我个人不是代码生成对象的忠实粉丝。

我必须继续补充这个答案。 尝试将您的消息传递视为一个命令,而不是代表您的数据库的数据实体。我会给你一个例子,说明我的一个简单类和一个对我来说非常有效的基础设施决策:

[Serializable]
public class AddMediaCategoryRequest : IRequest<AddMediaCategoryResponse>
{
    private readonly Guid _parentCategory;
    private readonly string _label;
    private readonly string _description;

    public AddMediaCategoryRequest(Guid parentCategory, string label, string description)
    {
        _parentCategory = parentCategory;
        _description = description;
        _label = label;
    }

    public string Description
    {
        get { return _description; }
    }

    public string Label
    {
        get { return _label; }
    }

    public Guid ParentCategory
    {
        get { return _parentCategory; }
    }
}

[Serializable]
public class AddMediaCategoryResponse : Response 
{
    public Guid ID;
}


public interface IRequest<T> : IRequest where T : Response, new() {}


[Serializable]
public class Response
{
    protected bool _success;
    private string _failureMessage = "This is the default error message.  If a failure has been reported, it should have overwritten this message.";
    private Exception _exception;

    public Response()
    {
        _success = false;
    }

    public Response(bool success)
    {
        _success = success;
    }

    public Response(string failureMessage)
    {
        _failureMessage = failureMessage;
    }

    public Response(string failureMessage, Exception exception)
    {
        _failureMessage = failureMessage;
        _exception = exception;
    }

    public bool Success
    {
        get { return _success; }
    }

    public string FailureMessage
    {
        get { return _failureMessage; }
    }

    public Exception Exception
    {
        get { return _exception; }
    }

    public void Failed(string failureMessage)
    {
        _success = false;
        _failureMessage = failureMessage;
    }

    public void Failed(string failureMessage, Exception exception)
    {
        _success = false;
        _failureMessage = failureMessage;
        _exception = exception;
    }
}


public class AddMediaCategoryRequestHandler : IRequestHandler<AddMediaCategoryRequest,AddMediaCategoryResponse>
{
    private readonly IMediaCategoryRepository _mediaCategoryRepository;
    public AddMediaCategoryRequestHandler(IMediaCategoryRepository mediaCategoryRepository)
    {
        _mediaCategoryRepository = mediaCategoryRepository;
    }

    public AddMediaCategoryResponse HandleRequest(AddMediaCategoryRequest request)
    {
        MediaCategory parentCategory = null;
        MediaCategory mediaCategory = new MediaCategory(request.Description, request.Label,false);
        Guid id = _mediaCategoryRepository.Save(mediaCategory);
        if(request.ParentCategory!=Guid.Empty)
        {
            parentCategory = _mediaCategoryRepository.Get(request.ParentCategory);
            parentCategory.AddCategoryTo(mediaCategory);
        }
        AddMediaCategoryResponse response = new AddMediaCategoryResponse();
        response.ID = id;
        return response;
    }
}

我知道这种情况会一直持续下去,但这个基本系统在过去一年左右的时间里对我很有帮助

您可以看到处理程序允许域对象处理特定于域的逻辑

【讨论】:

  • 您好 Painless:能否麻烦您将上述 C# 类的内容删掉,以便我能更清楚地看到您的建议?谢谢!
  • 感谢您对此进行扩展。行。因此,不是传递 DTO/POCO/表的表示,而是传递一个消息结构......有点类似于 EventArgs 包,而是一个 CommandArgs 包,包含(在我的情况下)可能包含我想要的消息记录的 Guid背部。然后,您发回一个包含 XXX 的 ResponseArgs 包。在您的简单情况下,XXX 是一个值类型(ID)——但是当为消息返回内容时,我们是否返回 DbMessageRecord?或者您会将其展平为参数(无依赖性),以便在 BL 中重新组装成 Message?还是使用其他地方定义的 DTO?
  • 我明天回办公室回来,在这里给你一个适当的布局
  • 所以这个概念是 todo 上下文特定的消息。在消息中说明您的意图,处理程序完全特定于消息的意图,在这种情况下是请求响应引擎。因此,当我在这种情况下添加新记录时,我会将 guid 发送回 UI、Web 应用程序等,然后它可以在另一条消息 GetMediaCategyRequest 中请求该项目。
  • ORM 部分从其存储库中水合一个域对象,该对象被注入到类中,使用类似城堡的东西,我有一个启动程序,它找到所有依赖的程序集加载它们然后注入它们的依赖项并注册所有应用程序的处理程序,以便当消息通过管道时,它知道将其发送到哪里。使用 NHibernate 从存储库中对域对象进行水合后(如您所见,我使用所有存储库的接口,因此如果需要,我可以重构 NHibernate)
【解决方案2】:

您似乎缺少的概念是 IoC / DI(即控制反转 / 依赖注入)。而不是使用静态方法,你的每一层应该只依赖于下一层的接口,并将实际实例注入到构造函数中。您可以将您的 DL 称为存储库、提供程序或其他任何东西,只要它是底层持久性机制的干净抽象即可。

至于代表实体的对象(大致映射到表),我强烈建议不要使用两组对象(一组特定于数据库,另一组不是)。只要它们是 POCO(它们不应该真正知道它们是持久化的),或者甚至是 DTO(没有任何行为的纯结构),它们就可以被所有三层引用。使它们 DTO 更适合您的 BL 概念,但是我更喜欢让我的业务逻辑分布在我的域对象(“OOP 风格”)而不是 BL 的概念(“Microsoft 风格”)。

不确定 Llblgen,但 NHibernate + 任何 IoC(如 SpringFramework.NET 或 Windsor)都提供了非常干净的模型来支持这一点。

【讨论】:

    【解决方案3】:

    这可能是一个过于间接的答案,但去年我在 Java 世界中遇到过这类问题,发现 Martin Fowler's Patterns of Enterprise Application Architecture 很有帮助(另见他的 pattern catalog)。许多模式处理您正在努力解决的相同问题。它们都非常抽象,帮助我整理思路,以便能够在更高的层次上看到问题。

    我选择了一种使用 iBatis SQL 映射器来封装我们与数据库的交互的方法。 (SQL 映射器从 SQL 表驱动编程语言数据模型,而像您这样的 ORM 则相反。)SQL 映射器返回数据传输对象的列表和层次结构,每个对象代表一些查询结果的一行。查询(以及插入、更新、删除)的参数也作为 DTO 传入。 BL 层在 SQL Mapper 上进行调用(运行此查询、执行此插入等)并传递 DTO。 DTO 上升到表示层 (UI),在那里它们驱动模板扩展机制,生成数据的 XHTML、XML 和 JSON 表示。所以对我们来说,唯一流向 UI 的 DL 依赖项是一组 DTO,但它们使 UI 比传递未打包的字段值要简化得多。

    如果您将福勒的书与其他海报可以提供的具体帮助相结合,您会做得很好。这是一个有很多工具和经验的领域,所以应该有很多好的前进道路。

    编辑: @Ciel,你说的很对,DTO 实例只是一个 POCO(或者在我的情况下是一个 Java POJO)。 Person DTO 可以有一个名为“Jim”的名字字段,依此类推。每个 DTO 基本上对应于数据库表的一行,只是一组字段,仅此而已。这意味着它没有与 DL 紧密耦合,并且非常适合传递给 UI。福勒在第 4 页谈到了这些。 401(第一个剪牙的模式不错)。

    现在我没有使用 ORM,它获取您的数据对象并创建数据库。我正在使用 SQL 映射器,这只是在 SQL 中打包和执行数据库查询的一种非常有效和方便的方式。我首先设计了我的 SQL(我碰巧很了解它),然后我设计了我的 DTO,然后设置我的 iBatis 配置来说明“select * from Person where personid = #personid#”应该返回给我一个 Java 列表人员 DTO 对象。我还没有使用过 ORM(Hibernate,例如,在 Java 世界中),但是使用其中一个,您首先要创建数据模型对象,然后根据它们构建数据库。

    如果您的数据模型对象具有各种特定于 ORM 的附加组件,那么我可以理解为什么您在将它们暴露给 UI 层之前会三思而后行。但是你可以创建一个只定义 POCO get 和 set 方法的 C# 接口,并在所有非 DL API 中使用它,并创建一个包含所有 ORM 特定内容的实现类:

    interface Person ...
    
    class ORMPerson : Person ...
    

    然后,如果您稍后更改 ORM,您可以创建备用 POCO 实现:

    class NewORMPerson : Person ...
    

    这只会影响您的 DL 层代码,因为您的 BL 和 UI 代码使用 Person。

    @Zvolkov(下)建议将这种“编码到接口,而不是实现”的方法提升到一个新的水平,建议您可以以这样一种方式编写您的应用程序,即您的所有代码都使用 Person 对象,并且您可以使用dependency injection 框架来动态配置您的应用程序,以根据您当天要使用的 ORM 来创建 ORMPersons 或 NewORMPersons

    【讨论】:

    • 嗨,Jim:我有 PEAA,就在我的办公桌旁(上周由一位同事借来的):我几乎迷失了方向。我得到了简单的模式,但我发现我不太擅长抽象太多。我了解代码......还没有模式。 *这是我遇到的问题的根源......试图让足够多的模式无法理解以重新编写代码:-) 至于你写的......等等...... DTO只是代表表,(POCO)对吗?所以你一直在使用它们直到 UI 层......有点像使用 ORM 生成的类:没有spearation?
    • 或者您是否在 Orm 生成的类和 DTO 之间进行了映射?如果是这样,在 BL 还是 DL? (什么依赖,在什么方向?)
    【解决方案4】:

    尝试使用存储库模式集中所有数据访问。就您的实体而言,您可以尝试实现某种翻译层来映射您的实体,这样它就不会破坏您的应用程序。这只是暂时的,可以让您慢慢重构代码。

    显然我不知道您的代码库的全部范围,因此请考虑痛苦和收获。

    【讨论】:

    • 仓库应该定义在BL,还是DL?并返回 DbMessage 或 Message 对象?目前(在查找存储库模式的 def 之后)似乎我的 Providers 与该模式非常相似,提供隐藏对 Query 方法的任何引用的方法,只需要参数,并返回与底层存储机制无关的结果。唯一的问题——同样,它们返回的是什么,它们在哪里来回映射......或不?
    【解决方案5】:

    我的意见,YMMV。

    当我使用任何新技术时,我认为它应该满足两个标准,否则我就是在浪费时间。 (或者我理解的不够好。)

    1. 它应该简化事情,或者在最坏的情况下使它们不再复杂。

    2. 不应增加耦合或降低内聚性。

    听起来你觉得自己正朝着相反的方向前进,我知道这不是 LINQ 或 ORM 的意图。

    我自己对这种新东西的价值的看法是,它可以帮助开发人员将 DL 和 BL 之间的界限移到更抽象的领域。 DL 看起来不像原始表,而更像对象。就是这样。 (无论如何,我通常会非常努力地使用更重的 SQL 和存储过程来完成此操作,但我可能对 SQL 比一般人更舒服)。但是,如果 LINQ 和 ORM 还没有帮助您解决这个问题,我会说坚持下去,但这就是隧道的尽头;简化,并稍微移动抽象边界。

    【讨论】:

    • 嗨 Le Dorfier:应该会变得更容易......但是很多事情很容易......但不是很好的编码。我只是还没弄清楚 ORM 在这一切中的立场。拖鞋斜坡。如此简单(使用 Linq2SQL),但在混合中添加了延迟执行,我已经......没有关注点分离。只是......“馄饨”。 :-)
    猜你喜欢
    • 2011-05-05
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 2020-01-19
    • 2013-05-02
    • 2013-01-07
    • 1970-01-01
    • 2011-02-21
    相关资源
    最近更新 更多