Ryan LanciauxNew Media Mercenary

RhinoCommons, NHibernate and ASP.NET MVC Part 3 - The Model

May 22, 2008 by ryan

We're going to keep moving now that everything is setup (see part one for setup) and configured (see part two for configuration).

First off we are going to create our classes. The original classes and SQL tables are posted below (these may look familiar -- they are from my initial NHibernate post)


Initial Product

    public class Product

    {

        private IList<Product> _RelatedProducts;

        private IList<ProductGroup> _ProductGroups;

 

        public Product()

        {

            _RelatedProducts = new List<Product>();

            _ProductGroups = new List<ProductGroup>();

        }

 

        public virtual string ID { get; private set; }

        public virtual string Title { get; set; }

        public virtual string ImagePath { get; set; }

        public virtual string Description { get; set; }

 

        public virtual IList<Product> RelatedProducts

        {

            get { return _RelatedProducts; }

            set { _RelatedProducts = value; }

        }

        public virtual IList<ProductGroup> ProductGroups

        {

            get { return _ProductGroups; }

            set { _ProductGroups = value; }

        }

    }


Initial ProductGroup

    public class ProductGroup

    {

        public virtual string ProductGroupID { get; set; }

        public virtual string Title { get; set; }

        public virtual IList<Product> Products { get; set; }

    }


SQL Tables

CREATE TABLE [dbo].[SimpleProducts](

    [ProductID] [char](32) NOT NULL,

    [Title] [nvarchar](50) NOT NULL,

    [ImagePath] [nvarchar](300) NULL,

    [Description] [nvarchar](500) NULL

)

 

CREATE TABLE [dbo].[RelatedProductsLookup](

    [ProductID] [char](32) NOT NULL,

    [RelatedProductID] [char](32) NOT NULL

)

 

CREATE TABLE [dbo].[ProductsProductGroupsLookup](

    [ProductGroupID] [char](32) NULL,

    [ProductID] [char](32) NULL

)

 

CREATE TABLE [dbo].[ProductGroups](

    [ProductGroupID] [char](32) NOT NULL,

    [Title] [nvarchar](50) NULL


ActiveRecord Classes
In a traditional NHibernate application, we would write usually our mapping files at this time(see my other NHibernate post for more on that). Since we're using the ActiveRecord pattern, however, we can specify all our mappings inline with the classes. It is important to note that this would not be a pure domain because we're placing our mappings inside the model. Warning if you're sensitive to using Attributes this may not be the code for you...

Our classes will now begin with an ActiveRecord attribute over the class; our properties will begin with Property/HasAndBelongsToMany/etc. Please note, for the sake of the example, I'm being extremely verbose with my attributes. If your table/column names match the class/property names, some of the additional info in the attribute is not necessary.

    [ActiveRecord(Table="SimpleProducts")]

    public class Product

    {

        private IList<Product> _RelatedProducts;

        private IList<ProductGroup> _ProductGroups;

 

        public Product()

        {

            _RelatedProducts = new List<Product>();

            _ProductGroups = new List<ProductGroup>();

        }

 

 

        [PrimaryKey(Column="ProductID", Generator=Castle.ActiveRecord.PrimaryKeyType.UuidHex)]

        public virtual string ID { get; private set; }

 

        [Property(NotNull=true, Length=50, Column="Title")]

        public virtual string Title {get; set; }

 

        [Property(Length=300, NotNull=false, Column="ImagePath")]

        public virtual string ImagePath { get; set; }

 

        [Property(NotNull = false, Length = 500, Column="Description")]

        public virtual string Description { get; set; }

 

        [HasAndBelongsToMany(Table="RelatedProductsLookup", ColumnKey="ProductID", ColumnRef="RelatedProductID")]

        public virtual IList<Product> RelatedProducts

        {

            get { return _RelatedProducts; }

            set { _RelatedProducts = value; }

        }

 

        [HasAndBelongsToMany(Table="ProductsProductGroupsLookup", ColumnKey="ProductID", ColumnRef="ProductGroupID")]

        public virtual IList<ProductGroup> ProductGroups

        {

            get { return _ProductGroups; }

            set { _ProductGroups = value; }

        }

    }


    [ActiveRecord(Table="ProductGroups")]

    public class ProductGroup

    {

        [PrimaryKey(Column="ProductGroupID", Generator=Castle.ActiveRecord.PrimaryKeyType.UuidHex)]

        public virtual string ProductGroupID { get; set; }

 

        [Property(NotNull=true, Length=50, Column="Title")]

        public virtual string Title { get; set; }

 

        [HasAndBelongsToMany(Table="ProductsProductGroupsLookup", ColumnKey="ProductGroupID", ColumnRef="ProductID")]

        public virtual IList<Product> Products { get; set; }

    }


Repository Object
Another added benefit of using the Castle Active Record library is that we can use the Repository<T> for all of our object persistence. Instead of creating our own implementation of IRepository, we can write code like this to save / retrieve / update objects.

Selecting an object (our product IDs are HEX UUID's so this is not exactly accurate)

var p = Repository<Product>.Get(23); 


Saving / Updating

Repository<Product>.Save(p);


The repository is pretty nice -- we can save all of our objects outside of the domain, which makes for a much cleaner design. Next time, we'll be looking at the extremely simple MVC application powered by this model and NHibernate Query Generator. Continue to Part 4

 

kick it on DotNetKicks.com



Related posts

Comments

May 28. 2008 01:52

Michael Hanney

I was not aware you could declare the setter private like this:

public virtual string ID { get; private set; }

That's great. Do you need to do anything special to give AR/NHibernate access to this private setter, or does it just work?

Great blog engine too BTW.

Michael Hanney

May 28. 2008 08:00

Ryan Lanciaux

@Michael, Actually I didn't think too much about that when setting it up. It has just seemed to work with out any manual configuration.

Ryan Lanciaux

May 30. 2008 10:47

Anthony

Thanks so much for taking the time to write this series.

I don't suppose I could trouble you with a question?

In the case of the repositories i.e. var p = Repository<Product>.Get(23);

Would you consider putting custom methods in a repository (somehow)?
i.e. var p = Repository<Product>.GetByAge(23);

Or would you do this in some controller or service layer?

In other words what would you do!

Again, many thanks

Anthony

May 30. 2008 13:15

Ryan Lanciaux

@Anthony: My pleasure -- thanks for taking the time to read some of my posts Smile

I know in the examples I was calling the repository directly from the controller (more to keep the focus on getting everything setup) but in a production application I would probably create a service layer to achieve this sort of functionality. The main reason is that I could make the controller less coupled to the model.

Michael Hanney has an excellent post on a similar topic where he uses a service layer to abstract out some of the details -- michaelhanney.com/.../

Regards

Ryan Lanciaux

May 30. 2008 15:20

Michael Hanney

@Anthony,

I prefer to do a query like that inside a service layer facade to decouple the model from the controllers. I would also look to make a generic implementation of the GetByAge method for all classes that have an Age dimension. I would do this even if Product was the only class that had Age because my aim is generic static methods that make up a framework API I can reuse on other projects. The service layer is not coupled to a single model implementation, but it is tied to the Interfaces. Ultimately it depends on the resources available to the project I guess (time and money).

var p = Repository<Product>.GetByAge(23);

might become

var p = LifetimeService<Product>.GetByAge(23);

where LifetimeService defines

public static T GetByAge<T>(int age) where T : ILifetime
{
DetachedCriteria criteria = DetachedCriteria.For(typeof(T));
criteria.Add(Expression.Eq("Age", age));
return Repository<T>.FindAll(criteria);
}

(The Product class would have to implement ILifetime).

I know this looks like coding overhead, but when the logic gets more complicated - e.g., get by age where product is not discontinued - the service layer results in fewer lines of code and less duplication.

Slightly off topic question - is Age a hypothetical exmaple? I understand why you might have Age as a class property, but wouldn't it be a function of 'date of birth'?

Michael Hanney

May 31. 2008 14:54

Anthony

@Ryan, Michael

Thanks so much for taking the time out to answer my question.

I've been following your articles and have found them invaluable because I'm not quite clever enough to dig through Oren's code and figure out how to use it in my own implementations Frown

@Michael
The GetByAge was just a trivial example!

Anthony

Comments are closed




© 2008 Ryan Lanciaux :: powered by BlogEngine.NET