views:

251

answers:

3

I'm having trouble understanding how to use ORM generated objects. We're using LLBLGen for mapping our database model to objects. These objects we encapsulate in another layer which represents our business model(I think).

Maybe this bit of code will explain this better.

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
}

Exposing the entity's fields like properties feels awkward, since I'm mapping all over again. With list-types it's even much worse, since I have to write a "Add" and "Remove" method plus a property that exposes List.

In the above example in the BookType setter I need access to the BookTypeEntity object, I can get this object by instantiating a new one using the ID oh the BookType object. This also doesn't feel good.

I'm wondering if I shouldn't just extend the BookEntity object and add my business logic there? Or maybe use partials?

In the LLGLGen examples they use the entity objects directly, but this looks very messy to me. I want to have objects which can also have methods for my business logic(like CountPages) in the code above.

+2  A: 

Dunno if it's possible in LLGLGen, but what I generally do when working with ORMs is to create an interface to the persisted class, in your example IBook. I expose this interface via a public getter from the wrapping class. This way, if needs will be, you can extend you IBook the way you want if you need to add some custom behaviour to its fields.

Generally speaking, I think there's 3 ways of "mapping" your ORM-entities to your domain:

  1. The way you've posted. Basically, remap everything again
  2. The way I posted, expose the ORM-entity as an interface
  3. Expose the ORM-entity directly

I don't like #1, cause I don't like to have 2 mappings in my application. DRY, KISS and YAGNI are all violated by this.

I don't like #3 cause it would make whatever consumer-layer of your domain-layer talk directly to the ORM layer.

.. So, I go with #2, as it seems to be the lesser of 3 evils ;)

[Update] Small code snippet :)

ORM-generated class in the data-layer:

public class Book : IBook
{
   public string ISBN {get; private set;}
}

IBook is found in the business-logic layer, along with a book wrapper:

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

I don't know if this is a recognizable design-pattern, but it's what I use, and so far it has worked quite well :)

cwap
I don't fully understand method two. Would the exposed interface contain the Entity fields? Is this a pattern I can look up, or could you otherwise give a very small code-sample? Thanks a lot!
Robert Massa
Updated with a small out-of-my-head example.. I don't know if LLGLGen allows you to make Book derive from one of your interfaces.
cwap
Ah, now I understand what you mean. Unfortunately it is not possible to make LLBLGen generate classes that implement a interface.
Robert Massa
Aw - Well.. Good luck with it anyway :)
cwap
@Robert Massa: As long as it can generate partial classes it is possible to make it derive from your interfaces. And if you name the interface members the same as the data members your done!
rstevens
@Meeh: +1 Looks like a nice solution that could help me too!
rstevens
Just had an 'aha' moment for this solution. Actually using both of the answers. Thanks guys!
Robert Massa
The LLBLGen tool does support interfaces. Edit an entity and click on Code Gen Options > Output Specific Settings
jayrdub
+5  A: 

I've never used LLBLGen for mapping, but most of the ORM tools I've worked with generate partial classes. I then add any custom code/logic I'd like to add to those objects in a seperate file (so they don't get over-written if the partial classes are re-generated).

Seems to work pretty well. If you don't get partial classes from your ORM, I'd create a Facade which wraps your Data Object with your Business Logic...that way the two are seperated and you can re-gen one without overwriting the other.

UPDATE

Partial classes support implementing an Interface in one declaration of a partial class and not the other. If you want to implement an interface, you can implement it in your custom code partial file.

Straight from MSDN:

partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }

is equivilant to

class Earth : Planet, IRotate, IRevolve { }
Justin Niessner
LLBLGen does create partial classes indeed. But how would I use inheritance when all my business objects are partials of the Entity objects? (for example, I want my book to implement a interface Document)
Robert Massa
Updated with an answer to your question.
Justin Niessner
+1  A: 

You are feeling the pain of the mismatch between the different paradigms of relational data and objects.

By this, I mean that the worlds of relational data and objects are very, very different from each other. For example, in database-land all data is public. In object-land, data is encapsulated and very specifically not made public. In database-land, all relationships are bi-directional, whereas in object-land an object in a collection might not have any reference to its parent. In database-land, procedures are global. In object-land, procedures are local to the object which contains the acted-upon data.

For these reasons and more, an approach which creates objects that represent database tables is inherently going to be painful. While yes, technically they are objects, they have the semantics of database-land. Making them live in object-land, as you have experienced, is difficult if not impossible. This can be referred to as data-first.

A better approach (in my opinion) is to focus on mapping objects to the database, rather than mapping the database to objects. This can be referred to as object-first, and is supported very well by NHibernate. This approach emphasizes the fact that a database is an implementation detail of a system, not a design precept.

I realize this doesn't specifically answer your question, but I hope it provides some context as to why you are having a hard time conceptualizing your entities: they are tables first and entities second.

Bryan Watts
Thanks for your answer Bryan. I've come to realize exactly what you have lined out so nicely in your answer over the past few months. To bad I'm stuck with LLBLGen which has an data-first approach(although the next version should support object-first modeling).
Robert Massa