Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Repository pattern: Implementation and lazy loading of model relationships

I have an application which deals with products and product categories. For each of these I have models defined using POCO.

// Represents a product.
class Product {
  public virtual int ID { get; set; }
  public virtual string Name { get; set; }
  public virtual ProductCategory Category { get; set; }
}

// Represents a product category.
class ProductCategory {
  public virtual int ID { get; set; }
  public virtual string Name { get; set; }
  public virtual IEnumerable<Product> Products { get; set; }
}

The application uses a repository to access these models

// The interface implemented by the application's repository
interface IProductRepository {
  IEnumerable<Product> GetAllProducts();

  void Add(Product product);
  void Remove(Product product);
  void Save(Product product);
}

In the Product class, the property named Category of type ProductCategory should be loaded only when it is needed/accessed (lazy-loading). I want my models to remain POCO and contain only the structure of the model.

Am I taking the right approach ?
Should I have only the ID of the category in the Product class and use a separate repository for product categories to load the category ?

Implementing lazy-loading and relationships
For now my implementation of the repository interface returns an object of a type which extends the Product type and has support for lazy-loading through the repository instance.

Who should be responsible for loading the product category ?

I'm interested in how the product and category repositories should interact to achieve the lazy-loading ? Should they reference each other or should I have a main repository with the two sub repositories and pass that to my extended model types ?

What approach would you take ? (any suggestions and criticism is welcomed)


I should note that I want the application to be extensible and all the interfaces for the repositories and the models themselves will be in a separate asembly. This means that the extender will not have direct access to the model class definition.

like image 827
Cristian Toma Avatar asked May 10 '11 16:05

Cristian Toma


1 Answers

A few remarks and my opinions:

1)

Should I have only the ID of the category in the Product class and use a separate repository for product categories to load the category ?

No. You are using an ORM (at least I assume you do) to be able to model relationships by references between class instances and not by IDs you are using then to query in a relational fashion. Taking your idea to the last consequence would mean that you remove all navigation properties at all from the model classes and have only scalar properties and some of them act as keys between objects. That's only the "R" in ORM.

2)

For now my implementation of the repository interface returns an object of a type which extends the Product type and has support for lazy-loading through the repository instance.

Not sure what this means exactly. (I would like to see a code-snippet how you do that.) But my guess is that in your derived Product class you inject somehow a reference to the repository, like so:

public class ProductProxy : Product
{
    private IProductRepository _productRepo;

    public ProductProxy(IProductRepository productRepo)
    {
        _productRepo = productRepo;
    }

    // now you use _productRepo to lazily load something on request, do you?
}

Well, it's obviously a problem now to load the categories since IProductRepository doesn't have methods to access them.

3)

I'm interested in how the product and category repositories should interact to achieve the lazy-loading ? Should they reference each other or should I have a main repository with the two sub repositories and pass that to my extended model types ?

Your ProductRepository and CategoryRepository look like instances of a generic repository which is only responsible for a single entity type (in EF 4.1 this would be similar to DbSet<T> where T is Product or Category respectively).

I would avoid to have references between those repositories as this may end up in a hell of complex repo-references whenever you add new entities or navigation properties.

I see two other options:

  • (Basically what you already mentioned) Having a repository which is responsible for Product and Category together. You could still have your generic repositories but I would consider them more as internal helper repos and would only use them as private members inside of the main repository. This way you can have a group of repositories, each of them is responsible for some closely related entities.

  • Introduce a Unit of Work which is able to create all of your generic repositories (again in EF 4.1 this would be something like the factory method DbContext.Set<T>() where DbContext is the unit of work) and then inject this Unit of Work into your derived instances:

    public class ProductProxy : Product
    {
        private IUnitOfWork _unitOfWork;
    
        public ProductProxy(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }  
    
        public Category Category
        {
            get
            {
                // ...
                var productRepo = _unitOfWork.CreateGenericRepo<Product>();
                var categoryRepo = _unitOfWork.CreateGenericRepo<Category>();
                // you can pull out the repos you need and work with them
            }
            set { ... }
        }
    }
    

I would prefer the second option because in the first option you may end up in huge repositories to support loading of all possible relationships. Think of: Order has OrderItems, OrderItem has Product, Product has Category, Order has Customer, Customer has list of Addresses, Address has list of Contact Persons and so on and so forth...

4) (because you were also asking for criticism)

Are you writing your own ORM or are your writing an application? Your design goes into a direction which may become very complex and you are reinventing the wheel in my opinion. If you planning to use EF or NHibernate (or other ORM's) then you are creating functions which are already available out of the box, you only put abstractions on top of it which add no value. Lazy loading through dynamic proxies happens transparently in the sense that you never work explicitely with those proxies in your code, you always work with your POCO entities. They are invisible and exist only at runtime. Why do you want to develop your own lazy loading infrastructure?

like image 78
Slauma Avatar answered Oct 29 '22 14:10

Slauma