ORM和图层

时间:2009-05-28 22:24:41

标签: c# design-patterns orm

很抱歉这一点到处都是......但是我觉得这只狗正在追逐我的尾巴,我现在都很困惑。

我正在努力寻找开发3层解决方案(IL,BL,DL)的最简洁方法,其中DL使用ORM来抽象对数据库的访问。

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

然而,似乎还有一些基础需要数据存储机制不可知 - 看看LinqToSQL刚刚发生了什么:很多代码都是针对它编写的 - 仅针对MS 放弃它...所以我想尽可能地隔离ORM部分,只是不知道如何。

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

我开始的大会: UL.dll BL.dll DL.dll

主要课程:

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

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

每层功能:

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

BL反过来包裹DL。

DL有一个ProviderFactory,它包装了一个Provider实例。 DL.ProviderFactory暴露了(可能是我的一部分问题)两个静态方法   GetMessage(Guid id),和   SaveMessage(消息消息) 最终目标是能够将为Linq2SQL编写的提供程序替换为LLBLGen Pro或其他不支持ORM的提供程序(例如VistaDB)。

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

因此,这是否意味着:

a)消息在BL中定义 b)在DB中定义DB表的Db / Orm / Manual表示('DbMessageRecord',或'MessageEntity',或其他任何ORM调用它)。 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定义的,所以你不能撕掉一个Provider,并将其替换为另一个,...使整个练习毫无意义......不妨通过BL使用ORM的类。 e)或者......在BL和DL之间需要另一个汇编,并且需要另一个映射以使BL独立于底层DL类。

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

5 个答案:

答案 0 :(得分:2)

这是一个小小的地方,让我想起我第一次进入orm和DDD。 我个人使用核心域对象,消息传递对象,消息处理程序和存储库。 所以我的UI向处理程序发送一条消息,处理程序又通过存储库保存我的对象并执行该域对象中的业务逻辑。我使用NHibernate进行数据访问,使用FluentNHibernate进行类型绑定,而不是使用loosy goosey .hbm config。

因此,消息传递是我的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;
    }
}

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

你可以看到处理程序比允许域对象处理域特定逻辑

答案 1 :(得分:2)

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

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

不确定Llblgen,但是NHibernate +任何像SpringFramework.NET或Windsor这样的IoC都提供了非常干净的模型来支持它。

答案 2 :(得分:1)

这可能是一个过于间接的答案,但去年我在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可以具有first_name字段“Jim”,依此类推。每个DTO基本上对应于数据库表的一行,并且只是一组字段,仅此而已。这意味着它没有与DL紧密耦合,非常适合传递给UI。福勒谈到了这些。 401(不是第一个削减牙齿的模式)。

现在我没有使用ORM,它接收数据对象并创建数据库。我正在使用SQL映射器,这是在SQL中打包和执行数据库查询的一种非常有效且方便的方法。我首先设计了我的SQL(我碰巧知道它很好),然后我设计了我的DTO,然后设置我的iBatis配置来说,“select * from Person where personid =#personid#”应该给我一个Java List人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框架动态配置您的应用程序,以创建ORMPersons或NewORMPersons,具体取决于您当天要使用的ORM

答案 3 :(得分:0)

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

显然我不知道你的代码库的全部范围,所以考虑一下痛苦和收益。

答案 4 :(得分:0)

仅我的意见,YMMV。

当我搞乱任何新技术时,我认为它应该符合两个标准或者我在浪费时间。 (或者我不太了解它。)

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

  2. 不应增加耦合或降低凝聚力。

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

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