应该扩展还是封装ORM对象?

3

我不太明白如何使用ORM生成的对象。我们正在使用LLBLGen将数据库模型映射到对象。这些对象被封装在另一层中,代表我们的业务模型(我认为是这样的)。

也许这段代码能更好地解释这个问题。

public class Book { // The class as used in our application
    private BookEntity book;      // LLBLGen entity
    private BookType bookType;    // BookType is another class that wraps an entity

    public Book(int Id) {
        book = new BookEntity(Id);
    }

    public BookType BookType {
        get { return this.bookType; }
        set { 
            this.bookType = value; 
            this.book.BookType = new BookTypeEntity(value.ID);
            this.book.Save();
        }
    }

    public int CountPages() { }  // Example business method
}

暴露实体的字段(如属性)感觉很奇怪,因为我要重新映射一遍。对于列表类型来说,情况更糟,因为我还需要编写“添加”和“删除”方法以及公开List的属性。
在上面的示例中,在BookType setter中,我需要访问BookTypeEntity对象,我可以使用BookType对象的ID实例化一个新对象来获取此对象。这也不太好。
我想知道是否应该扩展BookEntity对象并在其中添加业务逻辑?或者使用partials?
在LLGLGen示例中,他们直接使用实体对象,但我觉得这看起来很乱。我希望代码中的对象也可以具有我的业务逻辑方法(例如CountPages)。
3个回答

5
我从未使用LLBLGen进行映射,但我使用过大多数ORM工具生成的部分类。然后,我会在一个单独的文件中添加任何想要添加到这些对象中的自定义代码/逻辑(以便在重新生成部分类时不会被覆盖)。
这种方法似乎非常有效。如果您的ORM没有生成部分类,我建议您创建一个Facade来包装您的数据对象和业务逻辑...这样两者就分开了,您可以重新生成其中一个而不会覆盖另一个。 更新 部分类支持在一个部分类的声明中实现接口,而在另一个部分类中则不是这样。如果您想要实现接口,可以在自定义代码的部分类文件中实现它。
直接从MSDN获取:
partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }

等同于

class Earth : Planet, IRotate, IRevolve { }

LLBLGen确实会创建部分类。但是当我的所有业务对象都是实体对象的部分类时,我该如何使用继承呢?(例如,我希望我的书实现一个名为Document的接口) - Robert Massa
已更新并回答了您的问题。 - Justin Niessner

3
不确定LLGLGen是否支持,但是在使用ORM时,我通常会创建一个接口来表示持久化类,例如IBook。我通过包装类的公共getter方法暴露这个接口。这样,如果需要的话,您可以按照需要扩展IBook,以添加一些自定义行为到其字段中。
一般来说,我认为有三种将ORM实体映射到域的方式:
1.您发布的方式。基本上,重新映射一切。 2.我发布的方式,将ORM实体公开为接口。 3.直接公开ORM实体。
我不喜欢#1,因为我不喜欢在我的应用程序中有两个映射。DRY、KISS和YAGNI都被违反了。
我不喜欢#3,因为它会使消费层的域层直接与ORM层交互。
所以,我选择#2,因为它似乎是三种恶中较小的一种 ;)
【更新】小代码片段 :)
数据层中的ORM生成类:
public class Book : IBook
{
   public string ISBN {get; private set;}
}

IBook被发现在业务逻辑层,与书籍包装器一起:

public interface IBook
{
    string ISBN {get;}
}

public class BookWrapper   //Or whatever you want to call it :)
{
    //Create a new book in the constructor
    public BookWrapper()
    {
        BookData = new Data.MyORM.CreateNewBook();
    }

    //Expose the IBook, so we dont have to cascade the ISBN calls to it
    public IBook BookData {get; protected set;}
    //Also add whichever business logic operation we need here
    public Author LookUpAuther()
    {
       if (IBook == null)
          throw new SystemException("Noes, this bookwrapper doesn't have an IBook :(")
       //Contact some webservice to find the author of the book, based on the ISBN
    }
}

我不知道这是否是一个公认的设计模式,但这是我使用的模式,到目前为止它已经运作得很好 :)


1
更新了一个小的脑海中的例子。我不知道LLGLGen是否允许您使Book从您的接口之一派生。 - cwap
1
嗯...好的,无论如何祝你好运 :) - cwap
1
只要它可以生成部分类,就可以使其从您的接口派生。如果您将接口成员命名为数据成员相同,那么问题就解决了! - mmmmmmmm
@Meeh:+1 看起来是个不错的解决方案,我也可以从中受益! - mmmmmmmm
1
LLBLGen工具支持接口。编辑实体并单击“代码生成选项”>“输出特定设置”。 - JeremyWeir
显示剩余3条评论

1

你正在感受到关系数据和对象之间不同范式的不匹配之痛。

我的意思是,关系数据和对象的世界非常非常不同。例如,在数据库领域,所有数据都是公开的。在对象领域中,数据被封装起来,非常明确地不公开。在数据库领域中,所有关系都是双向的,而在对象领域中,集合中的对象可能没有任何与其父级的引用。在数据库领域中,过程是全局的。在对象领域中,过程是局部的,属于包含所操作数据的对象。

出于这些原因和更多原因,创建代表数据库表的对象的方法本质上会很痛苦。虽然从技术上讲它们是对象,但它们具有数据库领域的语义。正如你所经历的那样,让它们在对象领域中生存是困难的,甚至是不可能的。这可以称为“数据优先”。

在我看来,更好的方法是专注于将对象映射到数据库,而不是将数据库映射到对象。这可以称为对象优先,并且NHibernate非常支持这种方法。这种方法强调了数据库是系统的实现细节,而不是设计原则。

我意识到这并没有直接回答你的问题,但我希望它提供了一些背景,解释了为什么你很难概念化你的实体:它们首先是表格,其次才是实体。


谢谢你的回答,Bryan。在过去几个月中,我已经意识到你在回答中所概述得很好的内容。很遗憾我被困在了 LLBLGen 中,它采用了数据优先的方法(尽管下一个版本应该支持对象优先建模)。 - Robert Massa

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