views:

92

answers:

2

I'm testing how difficult it is to use NHibernate with a domain that is completely unaware of it and isn't bent to accodomate any limitations.

On many examples I found on the web the domain being mapped is yet another example of an anemic domain, where objects don't go far from being simple data holders. Sure, this makes the mapping simple and all and it might appeal to data-centric persons/situations, but I don't like hearing the voices in my head that say "C has structs too, you know?", "Classes are not just fancy namespaces, you know?", or "Why don't you use CREATE TABLE instead?".

But back to NHibernate. NHibernate forces me to make properties virtual to be able to proxy them for lazy loading. This is something I don't mind, as I might need them as virtual for some AOP stuff too. The limitations I'm not happy with are the need for an empty constructor and the need for setters/properties. I want my entities to be created in a valid state, and most of the time that means no empty constructor. I also don't want to expose setters for collection properties, for the usual reasons. Oh, and setters for attributes that are not supposed to be changed directly.

Consider this simplified and contrived aggregate in a domain model somewhere:

public class ShoppingCartItem
{
    private readonly Product product;

    public ShoppingCartItem(Product product, int quantity)
    {
        if(quantity <= 0)
            throw new ArgumentOutOfRangeException("quantity");
        this.product = product;
        this.quantity = quantity;
    }
    public virtual Product Product
    {
        get { return product; }
    }
    private int quantity;
    public virtual int Quantity
    {
        get { return quantity; }
        set
        {
            if(value <= 0)
                throw new ArgumentOutOfRangeException("value");
            quantity = value;
    }

    public virtual Money TotalPrice
    {
        get { return product.Price * quantity; }
    }
}

public class ShoppingCart : Entity
{
    private readonly IDictionary<Product, ShoppingCartItem> items =
        new Dictionary<Product, ShoppingCartItem>();
    private readonly ISet<DiscountCoupon> discountCoupons =
        new HashSet<DiscountCoupon>();

    public virtual IEnumerable<ShoppingCartItem> Items
    {
        get { return items.Values; }
    }

    public virtual IEnumerable<DiscountCoupon> DiscountCoupons
    {
        get { return discountCoupons; }
    }

    public virtual void AddProduct(Product product)
    {
        ShoppingCartItem item;
        if(items.TryGetValue(product, out item))
            item.Quantity++;
        else
            items.Add(product, new ShoppingCartItem(product, 1));
    }

    public virtual void RemoveProduct(Product product)
    {
        ShoppingCartItem item;
        if(!items.TryGetValue(product, out item))
            throw new ArgumentException("product");

        if(item.Quantity == 1)
            items.Remove(product);
        else
            item.Quantity--;
    }

    public virtual int AddDiscountCoupon(DiscountCoupon coupon)
    {
        discountCoupons.Add(coupon);
    }

    public virtual int RemoveDiscountCoupon(DiscountCoupon coupon)
    {
        discountCoupons.Remove(coupon);
    }

    public virtual Money CalculatePrice()
    {
        // Missing complex discount logic
        return items.Values.Sum(item => item.TotalPrice);
    }
}

Most properties have no setter, and there are no empty constructors in sight. Collections are not meddled with directly, but through specialized methods. Can I make use of NHibernate's extensibility to map this? Or am I trying to hammer a screw again? Or both?

+5  A: 

Well you can put private/internal/protected setters on properties/collections and nhibernate will load them up properly (chapter 4.1.1)

The constructor has to be there but you are not obliged to make it a public one (chapter 4.1.2)

chapter ref from the latest http://sourceforge.net/projects/nhibernate/files/NHibernate/2.1.2GA/NHibernate-2.1.2.GA-reference.zip/download

Jaguar
+3  A: 

First off you can make the empty constructor private so that nobody else can access it. NH will still be able to get to it.

Second off NH can access your properties however you want.

access="backfield" is used for public virtual Product Product { get; private set; }

access="field.pascalcase-m-underscore" for m_Product

access="field.pascalcase-underscore" for _Product

There are other access strategies and I'm pretty sure you can even create your own if need be.

ShaneC
in fact, the access strategies are not always required to be defined explicitly
Jaguar

related questions