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

每层function:

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 / Orm / Manual表示(’DbMessageRecord’,或’MessageEntity’,或其他任何ORM调用它)在DL中定义。 c)BL依赖于DL d)在调用DL方法之前,没有ref或知道BL,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类。

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

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

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

我知道我可能会因为我的解释而受到惩罚,如果不清楚我会在以后进行辩护。

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

我必须继续添加这个答案。 尝试将您的消息传递视为一个命令而不是一个代表您的数据库的数据实体。 我将举例说明我的一个简单的课程和基础设施决定对我来说非常好,我不能相信:

 [Serializable] public class AddMediaCategoryRequest : IRequest { 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 : 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 { 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; } } 

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

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

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

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

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

这可能是一个间接的答案,但去年我在Java世界中遇到了这些问题,并发现Martin Fowler的企业应用程序架构模式非常有用(另见他的模式目录 )。 许多模式处理您正在努力解决的相同问题。 它们都非常抽象,帮助我组织思考,以便能够在更高层次上看到问题。

我选择了一种使用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

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

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

我的意见,YMMV。

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

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

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

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

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