During research to useful design-patterns for my Wcf Data Services, I came across the following which I’d like to share with you. In this case, it’s not about OData. For this, I’m still working to build a (rather abstract) Business Layer, first start at the Entity Framework – level.
One of the common methods to build a Business Component on top of your (EF) DAL is creating a facade to it using the Repository-Pattern. That is, simply define an interface:
public interface IRepository<T>
{
T GetById(int id);
IEnumerable<T> GetAll();
IQueryable<T> Query(Expression<Func<T, bool>> filter);
}
and then implementing it:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AdventureWorksDAL;
namespace AdventureWorksBC
{
public class ProductRepos : IRepository<Product>, IDisposable
{
private AdventureWorksEntities _context;
public ProductRepos()
{
_context = new AdventureWorksEntities();
var types = AdventureWorksEntities.GetKnownProxyTypes();
}
#region IRepository<Product> Members
public Product GetById(int id)
{
return _context.Product.Where(p => p.ProductID == id).FirstOrDefault();
}
public IEnumerable<Product> GetAll()
{
return _context.Product;
}
public IQueryable<Product> Query(System.Linq.Expressions.Expression<Func<Product, bool>> filter)
{
return _context.Product.Where(filter);
}
#endregion
#region IDisposable Members
public void Dispose()
{
if (_context != null)
{
_context.Dispose();
}
GC.SuppressFinalize(this);
}
#endregion
}
}
Ever since I’m lazy…. I want to take this to a higher abstract level. With the preceding solution, you always have to implement the Interface for every object in the datacontext. Of course you want to have BusinessComponents where you can leave specific rules, but those GetAll, GetById – methods are pretty common. Therefore we’ll have to write a base-class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects.DataClasses;
using System.Data.Metadata.Edm;
using AdventureWorksDAL;
using System.Data.Objects;
using System.Data;
using System.Linq.Expressions;
namespace AdventureWorksBC
{
public class RepositoryBase<T> : IDisposable where T : EntityObject, new()
{
//private IQueryable<T> _entity;
private AdventureWorksEntities _context;
private ObjectSet<T> _query;
public RepositoryBase()
{
_context = new AdventureWorksEntities();
_query = _context.CreateObjectSet<T>();
}
public T GetById(int id)
{
foreach (T entity in _query)
{
if ((int)entity.EntityKey.EntityKeyValues[0].Value == id)
{
return entity;
}
}
return null;
}
public IEnumerable<T> GetAll()
{
return _query;
}
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return _query.Where(filter);
}
#region IDisposable Members
public void Dispose()
{
if (_context != null)
{
_context.Dispose();
}
GC.SuppressFinalize(this);
}
#endregion
}
}
Well that’s our baseclass. Let me short explain;
public class RepositoryBase<T> : IDisposable where T : EntityObject, new()
As you see we’ve replaced the specific object (like “Product” or “Customer”) with a generic “T”. After this you see the constraint: “it must be of type EntityObject, and have a parameterless constructor”.
Then in the constructor we’re going to initialize our fields_: context and _query. _query now returns an ObjectSet of type T (also it grabs all of those entities from the context).
The methods “GetAll” and “Query” are somehow self explainable I think, so leave them for now.
What really is kind of tricky is “GetById(int id). Honestly I have not figured out yet a better way to do this, but the end justifies the means…
Well now look at a demo app to show what you could do with this base-class:
First define a few childclasses:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AdventureWorksDAL;
namespace AdventureWorksBC
{
public class ContactRepository : RepositoryBase<Contact>
{
public ContactRepository()
{
}
}
public class ProductRepository : RepositoryBase<Product>
{
public ProductRepository()
{
}
}
}
Now call our entities through our new BusinessComponent:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using AdventureWorksDAL;
using AdventureWorksBC;
using System.Data.Metadata.Edm;
namespace DemoEFBusinessComponent
{
class Program
{
static void Main(string[] args)
{
var productRepos = new ProductRepository();
Product product = productRepos.GetById(319);
Console.WriteLine(string.Format("productId: {0}",product.ProductID));
Console.WriteLine(string.Format("Name: {0}", product.Name));
Console.WriteLine(string.Format("Model: {0}", product.ProductNumber));
productRepos.Dispose();
Console.Read();
var contactRepos = new ContactRepository();
var contacts = contactRepos.GetAll();
foreach (Contact contact in contacts)
{
Console.WriteLine(string.Format("contactId: {0}", contact.ContactID));
Console.WriteLine(string.Format("LastName: {0}", contact.LastName));
Console.WriteLine(string.Format("Email: {0}", contact.EmailAddress));
Console.WriteLine("");
}
contactRepos.Dispose();
Console.Read();
var productCategoryRepos = new RepositoryBase<ProductCategory>();
var specificCategory = productCategoryRepos.Query(pc => pc.Name.StartsWith("co"));
foreach (ProductCategory category in specificCategory)
{
Console.WriteLine("productCategoryId: {0}", category.ProductCategoryID);
Console.WriteLine("productCategoryName: {0}", category.Name);
}
productCategoryRepos.Dispose();
Console.Read();
}
}
}
This turns out rather cool, since you don’t have to define childclasses like “public class ProductRepository : RepositoryBase” for instance. Obvious, but let me mention: It’s really up to business whether you need them.
If you try this at home please forgive me for the endless “adventureworks” contacts list!
Thank you:Gil Fink
Bhavesh @ .NET Blogger
August 20, 2010 at 9:51 am
This pattern is my favorite one. Always wanted to use it along with ORM and this example gives good idea. It would be nice to also implement Unit of Work pattern here.
Iain
September 6, 2011 at 10:01 pm
I dont understand why you are including the reference to the using AdventureWorksDAL; library in the application code surely the Application should never know about the DAL as it only comunicates with the BLL?
I’ve come across a similar situation myself today researching this very subject!