Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# - Object Composition - Removing Boilerplate Code

Context / Question

I've worked on numerous .NET projects that have been required to persist data and have usually ended up using a Repository pattern. Does anyone know of a good strategy for removing as much boilerplate code without sacrificing code base scalability?

Inheritance Strategy

Because so much of the Repository code is boiler plate and needs to be repeated I normally create a base class to cover the basics like exception handling, logging and transaction support as well as a few basic CRUD methods:

public abstract class BaseRepository<T> where T : IEntity {     protected void ExecuteQuery(Action query)     {         //Do Transaction Support / Error Handling / Logging         query();     }             //CRUD Methods:     public virtual T GetByID(int id){}     public virtual IEnumerable<T> GetAll(int id){}     public virtual void Add (T Entity){}     public virtual void Update(T Entity){}     public virtual void Delete(T Entity){} } 

So this works well when I have a simple domain, I can quickly create a DRY repository class for each entity. However, this starts to break down when the domain gets more complex. Lets say a new entity is introduced that does not allow updates. I can break up base classes and move the Update method into a different class:

public abstract class BaseRepositorySimple<T> where T : IEntity {     protected void ExecuteQuery(Action query);      public virtual T GetByID(int id){}     public virtual IEnumerable<T> GetAll(int id){}     public virtual void Add (T entity){}     public void Delete(T entity){} }  public abstract class BaseRepositoryWithUpdate<T> :     BaseRepositorySimple<T> where T : IEntity {      public virtual void Update(T entity){} } 

This solution does not scale well. Let's say I have several Entities that have a common method: public virtual void Archive(T entity){}

but some Entities that can be Archived can also be Updated while others can't. So my Inheritance solution breaks down, I'd have to create two new base classes to deal with this scenario.

Compoisition Strategy

I've explored the Compositon pattern, but this seems to leave a lot of boiler plate code:

public class MyEntityRepository : IGetByID<MyEntity>, IArchive<MyEntity> {     private Archiver<MyEntity> _archiveWrapper;           private GetByIDRetriever<MyEntity> _getByIDWrapper;      public MyEntityRepository()     {          //initialize wrappers (or pull them in          //using Constructor Injection and DI)     }      public MyEntity GetByID(int id)     {          return _getByIDWrapper(id).GetByID(id);     }      public void Archive(MyEntity entity)     {          _archiveWrapper.Archive(entity)'     } }  

The MyEntityRepository is now loaded with boilerplate code. Is there a tool / pattern that I can use to automatically generate this?

If I could turn the MyEntityRepository into something like this, I think that would by far be ideal:

[Implement(Interface=typeof(IGetByID<MyEntity>),      Using = GetByIDRetriever<MyEntity>)]       [Implement(Interface=typeof(IArchive<MyEntity>),      Using = Archiver<MyEntity>) public class MyEntityRepository {     public MyEntityRepository()     {          //initialize wrappers (or pull them in          //using Constructor Injection and DI)     } } 

Aspect Oriented Programming

I looked into using an AOP framework for this, specifically PostSharp and their Composition Aspect, which looks like it should do the trick, but in order to use a Repository I'll have to call Post.Cast<>(), which adds a very odd smell to the code. Anyone know if there's a better way to use AOP to help get rid of the compositor boilerplate code?

Custom Code Generator

If all else fails, I suppose I could work at creating a Custom Code Generator Visual Studio plug in that could generate the boiler plate code into a partial code file. Is there already a tool out there that would do this?

[Implement(Interface=typeof(IGetByID<MyEntity>),      Using = GetByIDRetriever<MyEntity>)]       [Implement(Interface=typeof(IArchive<MyEntity>),      Using = Archiver<MyEntity>) public partial class MyEntityRepository {     public MyEntityRepository()     {          //initialize wrappers (or pull them in          //using Constructor Injection and DI)     } }   //Generated Class file public partial class MyEntityRepository : IGetByID<MyEntity>, IArchive<MyEntity> {     private Archiver<MyEntity> _archiveWrapper;           private GetByIDRetriever<MyEntity> _getByIDWrapper;      public MyEntity GetByID(int id)     {          return _getByIDWrapper(id).GetByID(id);     }      public void Archive(MyEntity entity)     {          _archiveWrapper.Archive(entity)'     } }  

Extension Methods

Forgot to add this when I initially wrote the question (sorry). I also tried experimenting with extension methods:

public static class GetByIDExtenions {      public T GetByID<T>(this IGetByID<T> repository, int id){ }         } 

However, this has two problems, a) I'd have to remember the namespace of the extension methods class and add it everywhere and b) the extension methods can't satisfy interface dependencies:

public interface IMyEntityRepository : IGetByID<MyEntity>{} public class MyEntityRepository : IMyEntityRepository{} 

Update: Would T4 Templates be a possible solution?

like image 791
Philip Pittle Avatar asked Mar 16 '13 17:03

Philip Pittle


People also ask

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

What do you mean by C?

C is a structured, procedural programming language that has been widely used both for operating systems and applications and that has had a wide following in the academic community. Many versions of UNIX-based operating systems are written in C.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

What is C language used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...


1 Answers

I have a single generic repository interface, which is implemented only once for a particular data storage. Here it is:

public interface IRepository<T> where T : class {     IQueryable<T> GetAll();     T Get(object id);     void Save(T item);     void Delete(T item); } 

I have implementations of it for EntityFramework, NHibernate, RavenDB storages. Also I have an in-memory implementation for unit testing.

For example, here is a part of the in-memory collection-based repository:

public class InMemoryRepository<T> : IRepository<T> where T : class {     protected readonly List<T> _list = new List<T>();      public virtual IQueryable<T> GetAll()     {         return _list.AsReadOnly().AsQueryable();     }      public virtual T Get(object id)     {         return _list.FirstOrDefault(x => GetId(x).Equals(id));     }      public virtual void Save(T item)     {         if (_list.Any(x => EqualsById(x, item)))         {             Delete(item);         }          _list.Add(item);     }      public virtual void Delete(T item)     {         var itemInRepo = _list.FirstOrDefault(x => EqualsById(x, item));          if (itemInRepo != null)         {             _list.Remove(itemInRepo);         }     } } 

Generic repository interface frees me from creating lot's of similar classes. You have only one generic repository implementation, but also freedom in querying.

IQueryable<T> result from GetAll() method allows me to make any queries I want with the data, and separate them from the storage-specific code. All popular .NET ORMs have their own LINQ providers, and they all should have that magic GetAll() method - so no problems here.

I specify repository implementation in the composition root using IoC container:

ioc.Bind(typeof (IRepository<>)).To(typeof (RavenDbRepository<>)); 

In the tests I'm using it's in-memory replacement:

ioc.Bind(typeof (IRepository<>)).To(typeof (InMemoryRepository<>)); 

If I want to add more business-specific queries for the repository, I will add an extension method (similar to your extension method in the answer):

public static class ShopQueries {     public IQueryable<Product> SelectVegetables(this IQueryable<Product> query)     {         return query.Where(x => x.Type == "Vegetable");     }      public IQueryable<Product> FreshOnly(this IQueryable<Product> query)     {         return query.Where(x => x.PackTime >= DateTime.Now.AddDays(-1));     } } 

So you can use and mix those methods in the business logic layer queries, saving testability and easiness of repository implementations, like:

var freshVegetables = repo.GetAll().SelectVegetables().FreshOnly(); 

If you don't want to use a different namespace for those extension methods (like me) - ok, put them in the same namespace where repository implementation resides (like MyProject.Data), or, even better, to some existing business specific namespace (like MyProject.Products or MyProject.Data.Products). No need to remember additional namespaces now.

If you have some specific repository logic for some kind of entities, create a derived repository class overriding the method you want. For example, if products can only be found by ProductNumber instead of Id and don't support deleting, you can create this class:

public class ProductRepository : RavenDbRepository<Product> {     public override Product Get(object id)     {         return GetAll().FirstOrDefault(x => x.ProductNumber == id);     }      public override Delete(Product item)     {         throw new NotSupportedException("Products can't be deleted from db");     } } 

And make IoC return this specific repository implementation for products:

ioc.Bind(typeof (IRepository<>)).To(typeof (RavenDbRepository<>)); ioc.Bind<IRepository<Product>>().To<ProductRepository>(); 

That's how I leave in piece with my repositories ;)

like image 147
whyleee Avatar answered Oct 08 '22 17:10

whyleee